Merge "Close notification panel when user button is tapped" into nyc-dev
diff --git a/api/current.txt b/api/current.txt
index 936ca88..8a83024 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5723,6 +5723,7 @@
method public android.graphics.drawable.Drawable getFastDrawable();
method public static android.app.WallpaperManager getInstance(android.content.Context);
method public android.os.ParcelFileDescriptor getWallpaperFile(int);
+ method public int getWallpaperId(int);
method public android.app.WallpaperInfo getWallpaperInfo();
method public boolean hasResourceWallpaper(int);
method public boolean isWallpaperSettingAllowed();
@@ -7101,9 +7102,10 @@
method public void onServicesDiscovered(android.bluetooth.BluetoothGatt, int);
}
- public class BluetoothGattCharacteristic {
+ public class BluetoothGattCharacteristic implements android.os.Parcelable {
ctor public BluetoothGattCharacteristic(java.util.UUID, int, int);
method public boolean addDescriptor(android.bluetooth.BluetoothGattDescriptor);
+ method public int describeContents();
method public android.bluetooth.BluetoothGattDescriptor getDescriptor(java.util.UUID);
method public java.util.List<android.bluetooth.BluetoothGattDescriptor> getDescriptors();
method public java.lang.Float getFloatValue(int, int);
@@ -7121,6 +7123,8 @@
method public boolean setValue(int, int, int, int);
method public boolean setValue(java.lang.String);
method public void setWriteType(int);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothGattCharacteristic> CREATOR;
field public static final int FORMAT_FLOAT = 52; // 0x34
field public static final int FORMAT_SFLOAT = 50; // 0x32
field public static final int FORMAT_SINT16 = 34; // 0x22
@@ -7151,13 +7155,16 @@
field protected java.util.List<android.bluetooth.BluetoothGattDescriptor> mDescriptors;
}
- public class BluetoothGattDescriptor {
+ public class BluetoothGattDescriptor implements android.os.Parcelable {
ctor public BluetoothGattDescriptor(java.util.UUID, int);
+ method public int describeContents();
method public android.bluetooth.BluetoothGattCharacteristic getCharacteristic();
method public int getPermissions();
method public java.util.UUID getUuid();
method public byte[] getValue();
method public boolean setValue(byte[]);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothGattDescriptor> CREATOR;
field public static final byte[] DISABLE_NOTIFICATION_VALUE;
field public static final byte[] ENABLE_INDICATION_VALUE;
field public static final byte[] ENABLE_NOTIFICATION_VALUE;
@@ -7200,16 +7207,19 @@
method public void onServiceAdded(int, android.bluetooth.BluetoothGattService);
}
- public class BluetoothGattService {
+ public class BluetoothGattService implements android.os.Parcelable {
ctor public BluetoothGattService(java.util.UUID, int);
method public boolean addCharacteristic(android.bluetooth.BluetoothGattCharacteristic);
method public boolean addService(android.bluetooth.BluetoothGattService);
+ method public int describeContents();
method public android.bluetooth.BluetoothGattCharacteristic getCharacteristic(java.util.UUID);
method public java.util.List<android.bluetooth.BluetoothGattCharacteristic> getCharacteristics();
method public java.util.List<android.bluetooth.BluetoothGattService> getIncludedServices();
method public int getInstanceId();
method public int getType();
method public java.util.UUID getUuid();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothGattService> CREATOR;
field public static final int SERVICE_TYPE_PRIMARY = 0; // 0x0
field public static final int SERVICE_TYPE_SECONDARY = 1; // 0x1
field protected java.util.List<android.bluetooth.BluetoothGattCharacteristic> mCharacteristics;
@@ -17750,9 +17760,6 @@
method public android.icu.text.UnicodeSet addAll(java.lang.Iterable<?>);
method public android.icu.text.UnicodeSet addAll(T...);
method public T addAllTo(T);
- method public java.lang.String[] addAllTo(java.lang.String[]);
- method public static U addAllTo(java.lang.Iterable<T>, U);
- method public static T[] addAllTo(java.lang.Iterable<T>, T[]);
method public void addMatchSetTo(android.icu.text.UnicodeSet);
method public android.icu.text.UnicodeSet applyIntPropertyValue(int, int);
method public final android.icu.text.UnicodeSet applyPattern(java.lang.String);
@@ -17766,10 +17773,6 @@
method public android.icu.text.UnicodeSet cloneAsThawed();
method public android.icu.text.UnicodeSet closeOver(int);
method public android.icu.text.UnicodeSet compact();
- method public static int compare(java.lang.CharSequence, int);
- method public static int compare(int, java.lang.CharSequence);
- method public static int compare(java.lang.Iterable<T>, java.lang.Iterable<T>);
- method public static int compare(java.util.Collection<T>, java.util.Collection<T>, android.icu.text.UnicodeSet.ComparisonStyle);
method public int compareTo(android.icu.text.UnicodeSet);
method public int compareTo(android.icu.text.UnicodeSet, android.icu.text.UnicodeSet.ComparisonStyle);
method public int compareTo(java.lang.Iterable<java.lang.String>);
@@ -17812,7 +17815,6 @@
method public android.icu.text.UnicodeSet removeAll(android.icu.text.UnicodeSet);
method public android.icu.text.UnicodeSet removeAll(java.lang.Iterable<T>);
method public final android.icu.text.UnicodeSet removeAllStrings();
- method public static boolean resemblesPattern(java.lang.String, int);
method public android.icu.text.UnicodeSet retain(int, int);
method public final android.icu.text.UnicodeSet retain(int);
method public final android.icu.text.UnicodeSet retain(java.lang.CharSequence);
@@ -17827,7 +17829,6 @@
method public int spanBack(java.lang.CharSequence, android.icu.text.UnicodeSet.SpanCondition);
method public int spanBack(java.lang.CharSequence, int, android.icu.text.UnicodeSet.SpanCondition);
method public java.util.Collection<java.lang.String> strings();
- method public static java.lang.String[] toArray(android.icu.text.UnicodeSet);
method public java.lang.String toPattern(boolean);
field public static final int ADD_CASE_MAPPINGS = 4; // 0x4
field public static final android.icu.text.UnicodeSet ALL_CODE_POINTS;
@@ -17946,11 +17947,8 @@
ctor protected CECalendar(int, int, int);
ctor protected CECalendar(java.util.Date);
ctor protected CECalendar(int, int, int, int, int, int);
- method public static int ceToJD(long, int, int, int);
- method protected abstract int getJDEpochOffset();
method protected int handleComputeMonthStart(int, int, boolean);
method protected int handleGetLimit(int, int);
- method public static void jdToCE(int, int, int[]);
}
public abstract class Calendar implements java.lang.Cloneable java.lang.Comparable java.io.Serializable {
@@ -18178,7 +18176,6 @@
ctor public CopticCalendar(int, int, int);
ctor public CopticCalendar(java.util.Date);
ctor public CopticCalendar(int, int, int, int, int, int);
- method protected deprecated int getJDEpochOffset();
method protected deprecated int handleGetExtendedYear();
field public static final int AMSHIR = 5; // 0x5
field public static final int BABA = 1; // 0x1
@@ -18349,11 +18346,11 @@
ctor public IslamicCalendar(java.util.Date);
ctor public IslamicCalendar(int, int, int);
ctor public IslamicCalendar(int, int, int, int, int, int);
+ method public android.icu.util.IslamicCalendar.CalculationType getCalculationType();
method protected int handleComputeMonthStart(int, int, boolean);
method protected int handleGetExtendedYear();
method protected int handleGetLimit(int, int);
- method public boolean isCivil();
- method public void setCivil(boolean);
+ method public void setCalculationType(android.icu.util.IslamicCalendar.CalculationType);
field public static final int DHU_AL_HIJJAH = 11; // 0xb
field public static final int DHU_AL_QIDAH = 10; // 0xa
field public static final int JUMADA_1 = 4; // 0x4
@@ -18791,7 +18788,6 @@
method public int getMicro();
method public int getMilli();
method public int getMinor();
- method public static void main(java.lang.String[]);
field public static final android.icu.util.VersionInfo ICU_VERSION;
field public static final android.icu.util.VersionInfo UCOL_BUILDER_VERSION;
field public static final android.icu.util.VersionInfo UCOL_RUNTIME_VERSION;
diff --git a/api/system-current.txt b/api/system-current.txt
index f8863e5..2cad670 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5857,6 +5857,7 @@
method public android.graphics.drawable.Drawable getFastDrawable();
method public static android.app.WallpaperManager getInstance(android.content.Context);
method public android.os.ParcelFileDescriptor getWallpaperFile(int);
+ method public int getWallpaperId(int);
method public android.app.WallpaperInfo getWallpaperInfo();
method public boolean hasResourceWallpaper(int);
method public boolean isWallpaperSettingAllowed();
@@ -7376,9 +7377,10 @@
method public void onServicesDiscovered(android.bluetooth.BluetoothGatt, int);
}
- public class BluetoothGattCharacteristic {
+ public class BluetoothGattCharacteristic implements android.os.Parcelable {
ctor public BluetoothGattCharacteristic(java.util.UUID, int, int);
method public boolean addDescriptor(android.bluetooth.BluetoothGattDescriptor);
+ method public int describeContents();
method public android.bluetooth.BluetoothGattDescriptor getDescriptor(java.util.UUID);
method public java.util.List<android.bluetooth.BluetoothGattDescriptor> getDescriptors();
method public java.lang.Float getFloatValue(int, int);
@@ -7396,6 +7398,8 @@
method public boolean setValue(int, int, int, int);
method public boolean setValue(java.lang.String);
method public void setWriteType(int);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothGattCharacteristic> CREATOR;
field public static final int FORMAT_FLOAT = 52; // 0x34
field public static final int FORMAT_SFLOAT = 50; // 0x32
field public static final int FORMAT_SINT16 = 34; // 0x22
@@ -7426,13 +7430,16 @@
field protected java.util.List<android.bluetooth.BluetoothGattDescriptor> mDescriptors;
}
- public class BluetoothGattDescriptor {
+ public class BluetoothGattDescriptor implements android.os.Parcelable {
ctor public BluetoothGattDescriptor(java.util.UUID, int);
+ method public int describeContents();
method public android.bluetooth.BluetoothGattCharacteristic getCharacteristic();
method public int getPermissions();
method public java.util.UUID getUuid();
method public byte[] getValue();
method public boolean setValue(byte[]);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothGattDescriptor> CREATOR;
field public static final byte[] DISABLE_NOTIFICATION_VALUE;
field public static final byte[] ENABLE_INDICATION_VALUE;
field public static final byte[] ENABLE_NOTIFICATION_VALUE;
@@ -7475,16 +7482,19 @@
method public void onServiceAdded(int, android.bluetooth.BluetoothGattService);
}
- public class BluetoothGattService {
+ public class BluetoothGattService implements android.os.Parcelable {
ctor public BluetoothGattService(java.util.UUID, int);
method public boolean addCharacteristic(android.bluetooth.BluetoothGattCharacteristic);
method public boolean addService(android.bluetooth.BluetoothGattService);
+ method public int describeContents();
method public android.bluetooth.BluetoothGattCharacteristic getCharacteristic(java.util.UUID);
method public java.util.List<android.bluetooth.BluetoothGattCharacteristic> getCharacteristics();
method public java.util.List<android.bluetooth.BluetoothGattService> getIncludedServices();
method public int getInstanceId();
method public int getType();
method public java.util.UUID getUuid();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothGattService> CREATOR;
field public static final int SERVICE_TYPE_PRIMARY = 0; // 0x0
field public static final int SERVICE_TYPE_SECONDARY = 1; // 0x1
field protected java.util.List<android.bluetooth.BluetoothGattCharacteristic> mCharacteristics;
@@ -18922,9 +18932,6 @@
method public android.icu.text.UnicodeSet addAll(java.lang.Iterable<?>);
method public android.icu.text.UnicodeSet addAll(T...);
method public T addAllTo(T);
- method public java.lang.String[] addAllTo(java.lang.String[]);
- method public static U addAllTo(java.lang.Iterable<T>, U);
- method public static T[] addAllTo(java.lang.Iterable<T>, T[]);
method public void addMatchSetTo(android.icu.text.UnicodeSet);
method public android.icu.text.UnicodeSet applyIntPropertyValue(int, int);
method public final android.icu.text.UnicodeSet applyPattern(java.lang.String);
@@ -18938,10 +18945,6 @@
method public android.icu.text.UnicodeSet cloneAsThawed();
method public android.icu.text.UnicodeSet closeOver(int);
method public android.icu.text.UnicodeSet compact();
- method public static int compare(java.lang.CharSequence, int);
- method public static int compare(int, java.lang.CharSequence);
- method public static int compare(java.lang.Iterable<T>, java.lang.Iterable<T>);
- method public static int compare(java.util.Collection<T>, java.util.Collection<T>, android.icu.text.UnicodeSet.ComparisonStyle);
method public int compareTo(android.icu.text.UnicodeSet);
method public int compareTo(android.icu.text.UnicodeSet, android.icu.text.UnicodeSet.ComparisonStyle);
method public int compareTo(java.lang.Iterable<java.lang.String>);
@@ -18984,7 +18987,6 @@
method public android.icu.text.UnicodeSet removeAll(android.icu.text.UnicodeSet);
method public android.icu.text.UnicodeSet removeAll(java.lang.Iterable<T>);
method public final android.icu.text.UnicodeSet removeAllStrings();
- method public static boolean resemblesPattern(java.lang.String, int);
method public android.icu.text.UnicodeSet retain(int, int);
method public final android.icu.text.UnicodeSet retain(int);
method public final android.icu.text.UnicodeSet retain(java.lang.CharSequence);
@@ -18999,7 +19001,6 @@
method public int spanBack(java.lang.CharSequence, android.icu.text.UnicodeSet.SpanCondition);
method public int spanBack(java.lang.CharSequence, int, android.icu.text.UnicodeSet.SpanCondition);
method public java.util.Collection<java.lang.String> strings();
- method public static java.lang.String[] toArray(android.icu.text.UnicodeSet);
method public java.lang.String toPattern(boolean);
field public static final int ADD_CASE_MAPPINGS = 4; // 0x4
field public static final android.icu.text.UnicodeSet ALL_CODE_POINTS;
@@ -19118,11 +19119,8 @@
ctor protected CECalendar(int, int, int);
ctor protected CECalendar(java.util.Date);
ctor protected CECalendar(int, int, int, int, int, int);
- method public static int ceToJD(long, int, int, int);
- method protected abstract int getJDEpochOffset();
method protected int handleComputeMonthStart(int, int, boolean);
method protected int handleGetLimit(int, int);
- method public static void jdToCE(int, int, int[]);
}
public abstract class Calendar implements java.lang.Cloneable java.lang.Comparable java.io.Serializable {
@@ -19350,7 +19348,6 @@
ctor public CopticCalendar(int, int, int);
ctor public CopticCalendar(java.util.Date);
ctor public CopticCalendar(int, int, int, int, int, int);
- method protected deprecated int getJDEpochOffset();
method protected deprecated int handleGetExtendedYear();
field public static final int AMSHIR = 5; // 0x5
field public static final int BABA = 1; // 0x1
@@ -19521,11 +19518,11 @@
ctor public IslamicCalendar(java.util.Date);
ctor public IslamicCalendar(int, int, int);
ctor public IslamicCalendar(int, int, int, int, int, int);
+ method public android.icu.util.IslamicCalendar.CalculationType getCalculationType();
method protected int handleComputeMonthStart(int, int, boolean);
method protected int handleGetExtendedYear();
method protected int handleGetLimit(int, int);
- method public boolean isCivil();
- method public void setCivil(boolean);
+ method public void setCalculationType(android.icu.util.IslamicCalendar.CalculationType);
field public static final int DHU_AL_HIJJAH = 11; // 0xb
field public static final int DHU_AL_QIDAH = 10; // 0xa
field public static final int JUMADA_1 = 4; // 0x4
@@ -19963,7 +19960,6 @@
method public int getMicro();
method public int getMilli();
method public int getMinor();
- method public static void main(java.lang.String[]);
field public static final android.icu.util.VersionInfo ICU_VERSION;
field public static final android.icu.util.VersionInfo UCOL_BUILDER_VERSION;
field public static final android.icu.util.VersionInfo UCOL_RUNTIME_VERSION;
@@ -31648,6 +31644,7 @@
method public android.os.Bundle getUserRestrictions(android.os.UserHandle);
method public boolean hasUserRestriction(java.lang.String);
method public boolean isManagedProfile();
+ method public boolean isManagedProfile(int);
method public boolean isQuietModeEnabled(android.os.UserHandle);
method public boolean isSystemUser();
method public boolean isUserAGoat();
diff --git a/api/test-current.txt b/api/test-current.txt
index 6327dc2..d3a12ed 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -5727,6 +5727,7 @@
method public android.graphics.drawable.Drawable getFastDrawable();
method public static android.app.WallpaperManager getInstance(android.content.Context);
method public android.os.ParcelFileDescriptor getWallpaperFile(int);
+ method public int getWallpaperId(int);
method public android.app.WallpaperInfo getWallpaperInfo();
method public boolean hasResourceWallpaper(int);
method public boolean isWallpaperSettingAllowed();
@@ -7105,9 +7106,10 @@
method public void onServicesDiscovered(android.bluetooth.BluetoothGatt, int);
}
- public class BluetoothGattCharacteristic {
+ public class BluetoothGattCharacteristic implements android.os.Parcelable {
ctor public BluetoothGattCharacteristic(java.util.UUID, int, int);
method public boolean addDescriptor(android.bluetooth.BluetoothGattDescriptor);
+ method public int describeContents();
method public android.bluetooth.BluetoothGattDescriptor getDescriptor(java.util.UUID);
method public java.util.List<android.bluetooth.BluetoothGattDescriptor> getDescriptors();
method public java.lang.Float getFloatValue(int, int);
@@ -7125,6 +7127,8 @@
method public boolean setValue(int, int, int, int);
method public boolean setValue(java.lang.String);
method public void setWriteType(int);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothGattCharacteristic> CREATOR;
field public static final int FORMAT_FLOAT = 52; // 0x34
field public static final int FORMAT_SFLOAT = 50; // 0x32
field public static final int FORMAT_SINT16 = 34; // 0x22
@@ -7155,13 +7159,16 @@
field protected java.util.List<android.bluetooth.BluetoothGattDescriptor> mDescriptors;
}
- public class BluetoothGattDescriptor {
+ public class BluetoothGattDescriptor implements android.os.Parcelable {
ctor public BluetoothGattDescriptor(java.util.UUID, int);
+ method public int describeContents();
method public android.bluetooth.BluetoothGattCharacteristic getCharacteristic();
method public int getPermissions();
method public java.util.UUID getUuid();
method public byte[] getValue();
method public boolean setValue(byte[]);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothGattDescriptor> CREATOR;
field public static final byte[] DISABLE_NOTIFICATION_VALUE;
field public static final byte[] ENABLE_INDICATION_VALUE;
field public static final byte[] ENABLE_NOTIFICATION_VALUE;
@@ -7204,16 +7211,19 @@
method public void onServiceAdded(int, android.bluetooth.BluetoothGattService);
}
- public class BluetoothGattService {
+ public class BluetoothGattService implements android.os.Parcelable {
ctor public BluetoothGattService(java.util.UUID, int);
method public boolean addCharacteristic(android.bluetooth.BluetoothGattCharacteristic);
method public boolean addService(android.bluetooth.BluetoothGattService);
+ method public int describeContents();
method public android.bluetooth.BluetoothGattCharacteristic getCharacteristic(java.util.UUID);
method public java.util.List<android.bluetooth.BluetoothGattCharacteristic> getCharacteristics();
method public java.util.List<android.bluetooth.BluetoothGattService> getIncludedServices();
method public int getInstanceId();
method public int getType();
method public java.util.UUID getUuid();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothGattService> CREATOR;
field public static final int SERVICE_TYPE_PRIMARY = 0; // 0x0
field public static final int SERVICE_TYPE_SECONDARY = 1; // 0x1
field protected java.util.List<android.bluetooth.BluetoothGattCharacteristic> mCharacteristics;
@@ -17760,9 +17770,6 @@
method public android.icu.text.UnicodeSet addAll(java.lang.Iterable<?>);
method public android.icu.text.UnicodeSet addAll(T...);
method public T addAllTo(T);
- method public java.lang.String[] addAllTo(java.lang.String[]);
- method public static U addAllTo(java.lang.Iterable<T>, U);
- method public static T[] addAllTo(java.lang.Iterable<T>, T[]);
method public void addMatchSetTo(android.icu.text.UnicodeSet);
method public android.icu.text.UnicodeSet applyIntPropertyValue(int, int);
method public final android.icu.text.UnicodeSet applyPattern(java.lang.String);
@@ -17776,10 +17783,6 @@
method public android.icu.text.UnicodeSet cloneAsThawed();
method public android.icu.text.UnicodeSet closeOver(int);
method public android.icu.text.UnicodeSet compact();
- method public static int compare(java.lang.CharSequence, int);
- method public static int compare(int, java.lang.CharSequence);
- method public static int compare(java.lang.Iterable<T>, java.lang.Iterable<T>);
- method public static int compare(java.util.Collection<T>, java.util.Collection<T>, android.icu.text.UnicodeSet.ComparisonStyle);
method public int compareTo(android.icu.text.UnicodeSet);
method public int compareTo(android.icu.text.UnicodeSet, android.icu.text.UnicodeSet.ComparisonStyle);
method public int compareTo(java.lang.Iterable<java.lang.String>);
@@ -17822,7 +17825,6 @@
method public android.icu.text.UnicodeSet removeAll(android.icu.text.UnicodeSet);
method public android.icu.text.UnicodeSet removeAll(java.lang.Iterable<T>);
method public final android.icu.text.UnicodeSet removeAllStrings();
- method public static boolean resemblesPattern(java.lang.String, int);
method public android.icu.text.UnicodeSet retain(int, int);
method public final android.icu.text.UnicodeSet retain(int);
method public final android.icu.text.UnicodeSet retain(java.lang.CharSequence);
@@ -17837,7 +17839,6 @@
method public int spanBack(java.lang.CharSequence, android.icu.text.UnicodeSet.SpanCondition);
method public int spanBack(java.lang.CharSequence, int, android.icu.text.UnicodeSet.SpanCondition);
method public java.util.Collection<java.lang.String> strings();
- method public static java.lang.String[] toArray(android.icu.text.UnicodeSet);
method public java.lang.String toPattern(boolean);
field public static final int ADD_CASE_MAPPINGS = 4; // 0x4
field public static final android.icu.text.UnicodeSet ALL_CODE_POINTS;
@@ -17956,11 +17957,8 @@
ctor protected CECalendar(int, int, int);
ctor protected CECalendar(java.util.Date);
ctor protected CECalendar(int, int, int, int, int, int);
- method public static int ceToJD(long, int, int, int);
- method protected abstract int getJDEpochOffset();
method protected int handleComputeMonthStart(int, int, boolean);
method protected int handleGetLimit(int, int);
- method public static void jdToCE(int, int, int[]);
}
public abstract class Calendar implements java.lang.Cloneable java.lang.Comparable java.io.Serializable {
@@ -18188,7 +18186,6 @@
ctor public CopticCalendar(int, int, int);
ctor public CopticCalendar(java.util.Date);
ctor public CopticCalendar(int, int, int, int, int, int);
- method protected deprecated int getJDEpochOffset();
method protected deprecated int handleGetExtendedYear();
field public static final int AMSHIR = 5; // 0x5
field public static final int BABA = 1; // 0x1
@@ -18359,11 +18356,11 @@
ctor public IslamicCalendar(java.util.Date);
ctor public IslamicCalendar(int, int, int);
ctor public IslamicCalendar(int, int, int, int, int, int);
+ method public android.icu.util.IslamicCalendar.CalculationType getCalculationType();
method protected int handleComputeMonthStart(int, int, boolean);
method protected int handleGetExtendedYear();
method protected int handleGetLimit(int, int);
- method public boolean isCivil();
- method public void setCivil(boolean);
+ method public void setCalculationType(android.icu.util.IslamicCalendar.CalculationType);
field public static final int DHU_AL_HIJJAH = 11; // 0xb
field public static final int DHU_AL_QIDAH = 10; // 0xa
field public static final int JUMADA_1 = 4; // 0x4
@@ -18801,7 +18798,6 @@
method public int getMicro();
method public int getMilli();
method public int getMinor();
- method public static void main(java.lang.String[]);
field public static final android.icu.util.VersionInfo ICU_VERSION;
field public static final android.icu.util.VersionInfo UCOL_BUILDER_VERSION;
field public static final android.icu.util.VersionInfo UCOL_RUNTIME_VERSION;
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index e6c5768..86734b1 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -1814,7 +1814,7 @@
private void resizeStackUnchecked(int stackId, Rect bounds, int delayMs, boolean animate) {
try {
- mAm.resizeStack(stackId, bounds, false, false, animate);
+ mAm.resizeStack(stackId, bounds, false, false, animate, -1);
Thread.sleep(delayMs);
} catch (RemoteException e) {
showError("Error: resizing stack " + e);
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 5f317b0..f5d7e7e 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -820,7 +820,9 @@
final boolean allowResizeInDockedMode = data.readInt() == 1;
final boolean preserveWindows = data.readInt() == 1;
final boolean animate = data.readInt() == 1;
- resizeStack(stackId, r, allowResizeInDockedMode, preserveWindows, animate);
+ final int animationDuration = data.readInt();
+ resizeStack(stackId,
+ r, allowResizeInDockedMode, preserveWindows, animate, animationDuration);
reply.writeNoException();
return true;
}
@@ -3889,7 +3891,8 @@
}
@Override
public void resizeStack(int stackId, Rect r, boolean allowResizeInDockedMode,
- boolean preserveWindows, boolean animate) throws RemoteException {
+ boolean preserveWindows, boolean animate, int animationDuration)
+ throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@@ -3903,6 +3906,7 @@
data.writeInt(allowResizeInDockedMode ? 1 : 0);
data.writeInt(preserveWindows ? 1 : 0);
data.writeInt(animate ? 1 : 0);
+ data.writeInt(animationDuration);
mRemote.transact(RESIZE_STACK_TRANSACTION, data, reply, 0);
reply.readException();
data.recycle();
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 5ef03d1..5b7dae6 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1147,8 +1147,7 @@
}
private Drawable getManagedProfileIconForDensity(UserHandle user, int drawableId, int density) {
- UserInfo userInfo = getUserInfo(user.getIdentifier());
- if (userInfo != null && userInfo.isManagedProfile()) {
+ if (isManagedProfile(user.getIdentifier())) {
return getDrawableForDensity(drawableId, density);
}
return null;
@@ -1156,8 +1155,7 @@
@Override
public CharSequence getUserBadgedLabel(CharSequence label, UserHandle user) {
- UserInfo userInfo = getUserInfo(user.getIdentifier());
- if (userInfo != null && userInfo.isManagedProfile()) {
+ if (isManagedProfile(user.getIdentifier())) {
return Resources.getSystem().getString(
com.android.internal.R.string.managed_profile_label_badge, label);
}
@@ -2259,17 +2257,16 @@
return drawable;
}
- private int getBadgeResIdForUser(int userHandle) {
+ private int getBadgeResIdForUser(int userId) {
// Return the framework-provided badge.
- UserInfo userInfo = getUserInfo(userHandle);
- if (userInfo != null && userInfo.isManagedProfile()) {
+ if (isManagedProfile(userId)) {
return com.android.internal.R.drawable.ic_corp_icon_badge;
}
return 0;
}
- private UserInfo getUserInfo(int userHandle) {
- return getUserManager().getUserInfo(userHandle);
+ private boolean isManagedProfile(int userId) {
+ return getUserManager().isManagedProfile(userId);
}
/** {@hide} */
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index ed4bb28..536c4a8 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -1319,7 +1319,7 @@
return getLocalUri();
case COLUMN_LOCAL_FILENAME:
if (!mAccessFilename) {
- throw new IllegalArgumentException(
+ throw new SecurityException(
"COLUMN_LOCAL_FILENAME is deprecated;"
+ " use ContentResolver.openFileDescriptor() instead");
}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index d0c21f5..639c207 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -148,8 +148,23 @@
public boolean moveTaskToDockedStack(int taskId, int createMode, boolean toTop, boolean animate,
Rect initialBounds) throws RemoteException;
public boolean moveTopActivityToPinnedStack(int stackId, Rect bounds) throws RemoteException;
+
+ /**
+ * Resizes the input stack id to the given bounds.
+ *
+ * @param stackId Id of the stack to resize.
+ * @param bounds Bounds to resize the stack to or {@code null} for fullscreen.
+ * @param allowResizeInDockedMode True if the resize should be allowed when the docked stack is
+ * active.
+ * @param preserveWindows True if the windows of activities contained in the stack should be
+ * preserved.
+ * @param animate True if the stack resize should be animated.
+ * @param animationDuration The duration of the resize animation in milliseconds or -1 if the
+ * default animation duration should be used.
+ * @throws RemoteException
+ */
public void resizeStack(int stackId, Rect bounds, boolean allowResizeInDockedMode,
- boolean preserveWindows, boolean animate) throws RemoteException;
+ boolean preserveWindows, boolean animate, int animationDuration) throws RemoteException;
/**
* Moves all tasks from the docked stack in the fullscreen stack and puts the top task of the
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 1143c6a..2fc6533 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -62,6 +62,11 @@
out Bundle outParams, int userId);
/**
+ * Retrieve the given user's current wallpaper ID of the given kind.
+ */
+ int getWallpaperIdForUser(int which, int userId);
+
+ /**
* If the current system wallpaper is a live wallpaper component, return the
* information about that wallpaper. Otherwise, if it is a static image,
* simply return null.
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 52e5272..b52af27 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -726,6 +726,38 @@
}
/**
+ * Get the ID of the current wallpaper of the given kind. If there is no
+ * such wallpaper configured, returns a negative number.
+ *
+ * @param which The wallpaper whose ID is to be returned. Must be a single
+ * defined kind of wallpaper, either {@link #FLAG_SET_SYSTEM} or
+ * {@link #FLAG_SET_LOCK}.
+ * @return The positive numeric ID of the current wallpaper of the given kind,
+ * or a negative value if no such wallpaper is configured.
+ */
+ public int getWallpaperId(int which) {
+ return getWallpaperIdForUser(which, mContext.getUserId());
+ }
+
+ /**
+ * Get the ID of the given user's current wallpaper of the given kind. If there
+ * is no such wallpaper configured, returns a negative number.
+ * @hide
+ */
+ public int getWallpaperIdForUser(int which, int userId) {
+ try {
+ if (sGlobals.mService == null) {
+ Log.w(TAG, "WallpaperService not running");
+ return -1;
+ } else {
+ return sGlobals.mService.getWallpaperIdForUser(which, userId);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Gets an Intent that will launch an activity that crops the given
* image and sets the device's wallpaper. If there is a default HOME activity
* that supports cropping wallpapers, it will be preferred as the default.
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index fe5c45f..98d356b 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -145,19 +145,23 @@
* {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME} instead, although specifying only
* {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME} is still supported.
*
- * <p> The intent may also contain the following extras:
+ * <p>The intent may also contain the following extras:
* <ul>
- * <li> {@link #EXTRA_PROVISIONING_LOGO_URI}, optional </li>
- * <li> {@link #EXTRA_PROVISIONING_MAIN_COLOR}, optional </li>
+ * <li>{@link #EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE}, optional </li>
+ * <li>{@link #EXTRA_PROVISIONING_SKIP_ENCRYPTION}, optional, supported from
+ * {@link android.os.Build.VERSION_CODES#N}</li>
+ * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_LOGO_URI}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_MAIN_COLOR}, optional</li>
* </ul>
*
- * <p> When managed provisioning has completed, broadcasts are sent to the application specified
+ * <p>When managed provisioning has completed, broadcasts are sent to the application specified
* in the provisioning intent. The
* {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE} broadcast is sent in the
* managed profile and the {@link #ACTION_MANAGED_PROFILE_PROVISIONED} broadcast is sent in
* the primary profile.
*
- * <p> If provisioning fails, the managedProfile is removed so the device returns to its
+ * <p>If provisioning fails, the managedProfile is removed so the device returns to its
* previous state.
*
* <p>If launched with {@link android.app.Activity#startActivityForResult(Intent, int)} a
@@ -171,7 +175,6 @@
= "android.app.action.PROVISION_MANAGED_PROFILE";
/**
- * @hide
* Activity action: Starts the provisioning flow which sets up a managed user.
*
* <p>This intent will typically be sent by a mobile device management application (MDM).
@@ -180,16 +183,24 @@
* been completed. Use {@link #isProvisioningAllowed(String)} to check if provisioning is
* allowed.
*
- * <p>This intent should contain the extra
- * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}.
+ * <p>The intent contains the following extras:
+ * <ul>
+ * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}</li>
+ * <li>{@link #EXTRA_PROVISIONING_SKIP_ENCRYPTION}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_LOGO_URI}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_MAIN_COLOR}, optional</li>
+ * </ul>
*
- * <p> If provisioning fails, the device returns to its previous state.
+ * <p>If provisioning fails, the device returns to its previous state.
*
* <p>If launched with {@link android.app.Activity#startActivityForResult(Intent, int)} a
* result code of {@link android.app.Activity#RESULT_OK} implies that the synchronous part of
* the provisioning flow was successful, although this doesn't guarantee the full flow will
* succeed. Conversely a result code of {@link android.app.Activity#RESULT_CANCELED} implies
* that the user backed-out of provisioning, or some precondition for provisioning wasn't met.
+ *
+ * @hide
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_PROVISION_MANAGED_USER
@@ -220,11 +231,11 @@
* <li>{@link #EXTRA_PROVISIONING_MAIN_COLOR}, optional</li>
* </ul>
*
- * <p> When device owner provisioning has completed, an intent of the type
+ * <p>When device owner provisioning has completed, an intent of the type
* {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE} is broadcast to the
* device owner.
*
- * <p> If provisioning fails, the device is factory reset.
+ * <p>If provisioning fails, the device is factory reset.
*
* <p>A result code of {@link android.app.Activity#RESULT_OK} implies that the synchronous part
* of the provisioning flow was successful, although this doesn't guarantee the full flow will
@@ -288,14 +299,14 @@
* The primary benefit is that multiple non-system users are supported when provisioning using
* this form of device management.
*
- * <p> During device owner provisioning a device admin app is set as the owner of the device.
+ * <p>During device owner provisioning a device admin app is set as the owner of the device.
* A device owner has full control over the device. The device owner can not be modified by the
* user.
*
- * <p> A typical use case would be a device that is owned by a company, but used by either an
+ * <p>A typical use case would be a device that is owned by a company, but used by either an
* employee or client.
*
- * <p> An intent with this action can be sent only on an unprovisioned device.
+ * <p>An intent with this action can be sent only on an unprovisioned device.
* It is possible to check if provisioning is allowed or not by querying the method
* {@link #isProvisioningAllowed(String)}.
*
@@ -305,13 +316,15 @@
* <li>{@link #EXTRA_PROVISIONING_SKIP_ENCRYPTION}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_LOGO_URI}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_MAIN_COLOR}, optional</li>
* </ul>
*
- * <p> When device owner provisioning has completed, an intent of the type
+ * <p>When device owner provisioning has completed, an intent of the type
* {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE} is broadcast to the
* device owner.
*
- * <p> If provisioning fails, the device is factory reset.
+ * <p>If provisioning fails, the device is factory reset.
*
* <p>A result code of {@link android.app.Activity#RESULT_OK} implies that the synchronous part
* of the provisioning flow was successful, although this doesn't guarantee the full flow will
@@ -439,7 +452,7 @@
*
* <p> When this extra is set, the application must have exactly one device admin receiver.
* This receiver will be set as the profile or device owner and active admin.
-
+ *
* @see DeviceAdminReceiver
* @deprecated Use {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}. This extra is still
* supported, but only if there is only one device admin receiver in the package that requires
@@ -461,7 +474,7 @@
* <p>This component is set as device owner and active admin when device owner provisioning is
* started by an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE} or by an NFC
* message containing an NFC record with MIME type
- * {@link #MIME_TYPE_PROVISIONING_NFC}. For the NFC record, the component name should be
+ * {@link #MIME_TYPE_PROVISIONING_NFC}. For the NFC record, the component name must be
* flattened to a string, via {@link ComponentName#flattenToShortString()}.
*
* @see DeviceAdminReceiver
@@ -664,8 +677,8 @@
* the file at download location specified in
* {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}.
*
- * <p>Either this extra or {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM} should be
- * present. The provided checksum should match the checksum of the file at the download
+ * <p>Either this extra or {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM} must be
+ * present. The provided checksum must match the checksum of the file at the download
* location. If the checksum doesn't match an error will be shown to the user and the user will
* be asked to factory reset the device.
*
@@ -689,8 +702,8 @@
* {@link android.content.pm.PackageManager#getPackageArchiveInfo} with flag
* {@link android.content.pm.PackageManager#GET_SIGNATURES}.
*
- * <p>Either this extra or {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM} should be
- * present. The provided checksum should match the checksum of any signature of the file at
+ * <p>Either this extra or {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM} must be
+ * present. The provided checksum must match the checksum of any signature of the file at
* the download location. If the checksum does not match an error will be shown to the user and
* the user will be asked to factory reset the device.
*
@@ -715,11 +728,14 @@
= "android.app.action.MANAGED_PROFILE_PROVISIONED";
/**
- * A boolean extra indicating whether device encryption can be skipped as part of Device Owner
- * provisioning.
+ * A boolean extra indicating whether device encryption can be skipped as part of device owner
+ * or managed profile provisioning.
*
* <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} or an intent with action
* {@link #ACTION_PROVISION_MANAGED_DEVICE} that starts device owner provisioning.
+ *
+ * <p>From {@link android.os.Build.VERSION_CODES#N} onwards, this is also supported for an
+ * intent with action {@link #ACTION_PROVISION_MANAGED_PROFILE}.
*/
public static final String EXTRA_PROVISIONING_SKIP_ENCRYPTION =
"android.app.extra.PROVISIONING_SKIP_ENCRYPTION";
@@ -762,7 +778,7 @@
"android.app.extra.PROVISIONING_SKIP_USER_SETUP";
/**
- * This MIME type is used for starting the Device Owner provisioning.
+ * This MIME type is used for starting the device owner provisioning.
*
* <p>During device owner provisioning a device admin app is set as the owner of the device.
* A device owner has full control over the device. The device owner can not be modified by the
@@ -772,7 +788,7 @@
* <p> A typical use case would be a device that is owned by a company, but used by either an
* employee or client.
*
- * <p> The NFC message should be send to an unprovisioned device.
+ * <p> The NFC message must be sent to an unprovisioned device.
*
* <p>The NFC record must contain a serialized {@link java.util.Properties} object which
* contains the following properties:
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index ea2dca0..68442ea 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -197,109 +197,43 @@
}
/**
- * A new GATT service has been discovered.
- * The service is added to the internal list and the search
- * continues.
- * @hide
- */
- public void onGetService(String address, int srvcType,
- int srvcInstId, ParcelUuid srvcUuid) {
- if (VDBG) Log.d(TAG, "onGetService() - Device=" + address + " UUID=" + srvcUuid);
- if (!address.equals(mDevice.getAddress())) {
- return;
- }
- mServices.add(new BluetoothGattService(mDevice, srvcUuid.getUuid(),
- srvcInstId, srvcType));
- }
-
- /**
- * An included service has been found durig GATT discovery.
- * The included service is added to the respective parent.
- * @hide
- */
- public void onGetIncludedService(String address, int srvcType,
- int srvcInstId, ParcelUuid srvcUuid,
- int inclSrvcType, int inclSrvcInstId,
- ParcelUuid inclSrvcUuid) {
- if (VDBG) Log.d(TAG, "onGetIncludedService() - Device=" + address
- + " UUID=" + srvcUuid + " Included=" + inclSrvcUuid);
-
- if (!address.equals(mDevice.getAddress())) {
- return;
- }
- BluetoothGattService service = getService(mDevice,
- srvcUuid.getUuid(), srvcInstId, srvcType);
- BluetoothGattService includedService = getService(mDevice,
- inclSrvcUuid.getUuid(), inclSrvcInstId, inclSrvcType);
-
- if (service != null && includedService != null) {
- service.addIncludedService(includedService);
- }
- }
-
- /**
- * A new GATT characteristic has been discovered.
- * Add the new characteristic to the relevant service and continue
- * the remote device inspection.
- * @hide
- */
- public void onGetCharacteristic(String address, int srvcType,
- int srvcInstId, ParcelUuid srvcUuid,
- int charInstId, ParcelUuid charUuid,
- int charProps) {
- if (VDBG) Log.d(TAG, "onGetCharacteristic() - Device=" + address + " UUID=" +
- charUuid);
-
- if (!address.equals(mDevice.getAddress())) {
- return;
- }
- BluetoothGattService service = getService(mDevice, srvcUuid.getUuid(),
- srvcInstId, srvcType);
- if (service != null) {
- service.addCharacteristic(new BluetoothGattCharacteristic(
- service, charUuid.getUuid(), charInstId, charProps, 0));
- }
- }
-
- /**
- * A new GATT descriptor has been discovered.
- * Finally, add the descriptor to the related characteristic.
- * This should conclude the remote device update.
- * @hide
- */
- public void onGetDescriptor(String address, int srvcType,
- int srvcInstId, ParcelUuid srvcUuid,
- int charInstId, ParcelUuid charUuid,
- int descrInstId, ParcelUuid descUuid) {
- if (VDBG) Log.d(TAG, "onGetDescriptor() - Device=" + address + " UUID=" + descUuid);
-
- if (!address.equals(mDevice.getAddress())) {
- return;
- }
- BluetoothGattService service = getService(mDevice, srvcUuid.getUuid(),
- srvcInstId, srvcType);
- if (service == null) return;
-
- BluetoothGattCharacteristic characteristic = service.getCharacteristic(
- charUuid.getUuid(), charInstId);
- if (characteristic == null) return;
-
- characteristic.addDescriptor(new BluetoothGattDescriptor(
- characteristic, descUuid.getUuid(), descrInstId, 0));
- }
-
- /**
* Remote search has been completed.
* The internal object structure should now reflect the state
* of the remote device database. Let the application know that
* we are done at this point.
* @hide
*/
- public void onSearchComplete(String address, int status) {
+ public void onSearchComplete(String address, List<BluetoothGattService> services,
+ int status) {
if (DBG) Log.d(TAG, "onSearchComplete() = Device=" + address + " Status=" + status);
if (!address.equals(mDevice.getAddress())) {
return;
}
+
+ for (BluetoothGattService s : services) {
+ //services we receive don't have device set properly.
+ s.setDevice(mDevice);
+ }
+
+ mServices.addAll(services);
+
+ // Fix references to included services, as they doesn't point to right objects.
+ for (BluetoothGattService fixedService : mServices) {
+ ArrayList<BluetoothGattService> includedServices =
+ new ArrayList(fixedService.getIncludedServices());
+ fixedService.getIncludedServices().clear();
+
+ for(BluetoothGattService brokenRef : includedServices) {
+ BluetoothGattService includedService = getService(mDevice,
+ brokenRef.getUuid(), brokenRef.getInstanceId(), brokenRef.getType());
+ if (includedService != null) {
+ fixedService.addIncludedService(includedService);
+ } else {
+ Log.e(TAG, "Broken GATT database: can't find included service.");
+ }
+ }
+ }
+
try {
mCallback.onServicesDiscovered(BluetoothGatt.this, status);
} catch (Exception ex) {
@@ -312,11 +246,12 @@
* Updates the internal value.
* @hide
*/
- public void onCharacteristicRead(String address, int status, int srvcType,
- int srvcInstId, ParcelUuid srvcUuid,
- int charInstId, ParcelUuid charUuid, byte[] value) {
+ public void onCharacteristicRead(String address, int status, int handle, byte[] value) {
if (VDBG) Log.d(TAG, "onCharacteristicRead() - Device=" + address
- + " UUID=" + charUuid + " Status=" + status);
+ + " handle=" + handle + " Status=" + status);
+
+ Log.w(TAG, "onCharacteristicRead() - Device=" + address
+ + " handle=" + handle + " Status=" + status);
if (!address.equals(mDevice.getAddress())) {
return;
@@ -331,9 +266,7 @@
&& mAuthRetry == false) {
try {
mAuthRetry = true;
- mService.readCharacteristic(mClientIf, address,
- srvcType, srvcInstId, srvcUuid,
- charInstId, charUuid, AUTHENTICATION_MITM);
+ mService.readCharacteristic(mClientIf, address, handle, AUTHENTICATION_MITM);
return;
} catch (RemoteException e) {
Log.e(TAG,"",e);
@@ -342,13 +275,11 @@
mAuthRetry = false;
- BluetoothGattService service = getService(mDevice, srvcUuid.getUuid(),
- srvcInstId, srvcType);
- if (service == null) return;
-
- BluetoothGattCharacteristic characteristic = service.getCharacteristic(
- charUuid.getUuid(), charInstId);
- if (characteristic == null) return;
+ BluetoothGattCharacteristic characteristic = getCharacteristicById(mDevice, handle);
+ if (characteristic == null) {
+ Log.w(TAG, "onCharacteristicRead() failed to find characteristic!");
+ return;
+ }
if (status == 0) characteristic.setValue(value);
@@ -364,11 +295,9 @@
* Let the app know how we did...
* @hide
*/
- public void onCharacteristicWrite(String address, int status, int srvcType,
- int srvcInstId, ParcelUuid srvcUuid,
- int charInstId, ParcelUuid charUuid) {
+ public void onCharacteristicWrite(String address, int status, int handle) {
if (VDBG) Log.d(TAG, "onCharacteristicWrite() - Device=" + address
- + " UUID=" + charUuid + " Status=" + status);
+ + " handle=" + handle + " Status=" + status);
if (!address.equals(mDevice.getAddress())) {
return;
@@ -378,12 +307,7 @@
mDeviceBusy = false;
}
- BluetoothGattService service = getService(mDevice, srvcUuid.getUuid(),
- srvcInstId, srvcType);
- if (service == null) return;
-
- BluetoothGattCharacteristic characteristic = service.getCharacteristic(
- charUuid.getUuid(), charInstId);
+ BluetoothGattCharacteristic characteristic = getCharacteristicById(mDevice, handle);
if (characteristic == null) return;
if ((status == GATT_INSUFFICIENT_AUTHENTICATION
@@ -391,8 +315,7 @@
&& mAuthRetry == false) {
try {
mAuthRetry = true;
- mService.writeCharacteristic(mClientIf, address,
- srvcType, srvcInstId, srvcUuid, charInstId, charUuid,
+ mService.writeCharacteristic(mClientIf, address, handle,
characteristic.getWriteType(), AUTHENTICATION_MITM,
characteristic.getValue());
return;
@@ -415,21 +338,14 @@
* Updates the internal value.
* @hide
*/
- public void onNotify(String address, int srvcType,
- int srvcInstId, ParcelUuid srvcUuid,
- int charInstId, ParcelUuid charUuid,
- byte[] value) {
- if (VDBG) Log.d(TAG, "onNotify() - Device=" + address + " UUID=" + charUuid);
+ public void onNotify(String address, int handle, byte[] value) {
+ if (VDBG) Log.d(TAG, "onNotify() - Device=" + address + " handle=" + handle);
if (!address.equals(mDevice.getAddress())) {
return;
}
- BluetoothGattService service = getService(mDevice, srvcUuid.getUuid(),
- srvcInstId, srvcType);
- if (service == null) return;
- BluetoothGattCharacteristic characteristic = service.getCharacteristic(
- charUuid.getUuid(), charInstId);
+ BluetoothGattCharacteristic characteristic = getCharacteristicById(mDevice, handle);
if (characteristic == null) return;
characteristic.setValue(value);
@@ -445,12 +361,8 @@
* Descriptor has been read.
* @hide
*/
- public void onDescriptorRead(String address, int status, int srvcType,
- int srvcInstId, ParcelUuid srvcUuid,
- int charInstId, ParcelUuid charUuid,
- int descrInstId, ParcelUuid descrUuid,
- byte[] value) {
- if (VDBG) Log.d(TAG, "onDescriptorRead() - Device=" + address + " UUID=" + charUuid);
+ public void onDescriptorRead(String address, int status, int handle, byte[] value) {
+ if (VDBG) Log.d(TAG, "onDescriptorRead() - Device=" + address + " handle=" + handle);
if (!address.equals(mDevice.getAddress())) {
return;
@@ -460,16 +372,7 @@
mDeviceBusy = false;
}
- BluetoothGattService service = getService(mDevice, srvcUuid.getUuid(),
- srvcInstId, srvcType);
- if (service == null) return;
-
- BluetoothGattCharacteristic characteristic = service.getCharacteristic(
- charUuid.getUuid(), charInstId);
- if (characteristic == null) return;
-
- BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
- descrUuid.getUuid(), descrInstId);
+ BluetoothGattDescriptor descriptor = getDescriptorById(mDevice, handle);
if (descriptor == null) return;
if (status == 0) descriptor.setValue(value);
@@ -479,9 +382,7 @@
&& mAuthRetry == false) {
try {
mAuthRetry = true;
- mService.readDescriptor(mClientIf, address,
- srvcType, srvcInstId, srvcUuid, charInstId, charUuid,
- descrInstId, descrUuid, AUTHENTICATION_MITM);
+ mService.readDescriptor(mClientIf, address, handle, AUTHENTICATION_MITM);
return;
} catch (RemoteException e) {
Log.e(TAG,"",e);
@@ -501,11 +402,8 @@
* Descriptor write operation complete.
* @hide
*/
- public void onDescriptorWrite(String address, int status, int srvcType,
- int srvcInstId, ParcelUuid srvcUuid,
- int charInstId, ParcelUuid charUuid,
- int descrInstId, ParcelUuid descrUuid) {
- if (VDBG) Log.d(TAG, "onDescriptorWrite() - Device=" + address + " UUID=" + charUuid);
+ public void onDescriptorWrite(String address, int status, int handle) {
+ if (VDBG) Log.d(TAG, "onDescriptorWrite() - Device=" + address + " handle=" + handle);
if (!address.equals(mDevice.getAddress())) {
return;
@@ -515,16 +413,7 @@
mDeviceBusy = false;
}
- BluetoothGattService service = getService(mDevice, srvcUuid.getUuid(),
- srvcInstId, srvcType);
- if (service == null) return;
-
- BluetoothGattCharacteristic characteristic = service.getCharacteristic(
- charUuid.getUuid(), charInstId);
- if (characteristic == null) return;
-
- BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
- descrUuid.getUuid(), descrInstId);
+ BluetoothGattDescriptor descriptor = getDescriptorById(mDevice, handle);
if (descriptor == null) return;
if ((status == GATT_INSUFFICIENT_AUTHENTICATION
@@ -532,9 +421,8 @@
&& mAuthRetry == false) {
try {
mAuthRetry = true;
- mService.writeDescriptor(mClientIf, address,
- srvcType, srvcInstId, srvcUuid, charInstId, charUuid,
- descrInstId, descrUuid, characteristic.getWriteType(),
+ mService.writeDescriptor(mClientIf, address, handle,
+ descriptor.getCharacteristic().getWriteType(),
AUTHENTICATION_MITM, descriptor.getValue());
return;
} catch (RemoteException e) {
@@ -651,6 +539,37 @@
/**
+ * Returns a characteristic with id equal to instanceId.
+ * @hide
+ */
+ /*package*/ BluetoothGattCharacteristic getCharacteristicById(BluetoothDevice device, int instanceId) {
+ for(BluetoothGattService svc : mServices) {
+ for(BluetoothGattCharacteristic charac : svc.getCharacteristics()) {
+ Log.w(TAG, "getCharacteristicById() comparing " + charac.getInstanceId() + " and " + instanceId);
+ if (charac.getInstanceId() == instanceId)
+ return charac;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a descriptor with id equal to instanceId.
+ * @hide
+ */
+ /*package*/ BluetoothGattDescriptor getDescriptorById(BluetoothDevice device, int instanceId) {
+ for(BluetoothGattService svc : mServices) {
+ for(BluetoothGattCharacteristic charac : svc.getCharacteristics()) {
+ for(BluetoothGattDescriptor desc : charac.getDescriptors()) {
+ if (desc.getInstanceId() == instanceId)
+ return desc;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
* Register an application callback to start using GATT.
*
* <p>This is an asynchronous call. The callback {@link BluetoothGattCallback#onAppRegistered}
@@ -898,9 +817,7 @@
try {
mService.readCharacteristic(mClientIf, device.getAddress(),
- service.getType(), service.getInstanceId(),
- new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
- new ParcelUuid(characteristic.getUuid()), AUTHENTICATION_NONE);
+ characteristic.getInstanceId(), AUTHENTICATION_NONE);
} catch (RemoteException e) {
Log.e(TAG,"",e);
mDeviceBusy = false;
@@ -943,11 +860,8 @@
try {
mService.writeCharacteristic(mClientIf, device.getAddress(),
- service.getType(), service.getInstanceId(),
- new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
- new ParcelUuid(characteristic.getUuid()),
- characteristic.getWriteType(), AUTHENTICATION_NONE,
- characteristic.getValue());
+ characteristic.getInstanceId(), characteristic.getWriteType(),
+ AUTHENTICATION_NONE, characteristic.getValue());
} catch (RemoteException e) {
Log.e(TAG,"",e);
mDeviceBusy = false;
@@ -988,11 +902,8 @@
}
try {
- mService.readDescriptor(mClientIf, device.getAddress(), service.getType(),
- service.getInstanceId(), new ParcelUuid(service.getUuid()),
- characteristic.getInstanceId(), new ParcelUuid(characteristic.getUuid()),
- descriptor.getInstanceId(), new ParcelUuid(descriptor.getUuid()),
- AUTHENTICATION_NONE);
+ mService.readDescriptor(mClientIf, device.getAddress(),
+ descriptor.getInstanceId(), AUTHENTICATION_NONE);
} catch (RemoteException e) {
Log.e(TAG,"",e);
mDeviceBusy = false;
@@ -1032,12 +943,8 @@
}
try {
- mService.writeDescriptor(mClientIf, device.getAddress(), service.getType(),
- service.getInstanceId(), new ParcelUuid(service.getUuid()),
- characteristic.getInstanceId(), new ParcelUuid(characteristic.getUuid()),
- descriptor.getInstanceId(), new ParcelUuid(descriptor.getUuid()),
- characteristic.getWriteType(), AUTHENTICATION_NONE,
- descriptor.getValue());
+ mService.writeDescriptor(mClientIf, device.getAddress(), descriptor.getInstanceId(),
+ characteristic.getWriteType(), AUTHENTICATION_NONE, descriptor.getValue());
} catch (RemoteException e) {
Log.e(TAG,"",e);
mDeviceBusy = false;
@@ -1168,10 +1075,7 @@
try {
mService.registerForNotification(mClientIf, device.getAddress(),
- service.getType(), service.getInstanceId(),
- new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
- new ParcelUuid(characteristic.getUuid()),
- enable);
+ characteristic.getInstanceId(), enable);
} catch (RemoteException e) {
Log.e(TAG,"",e);
return false;
diff --git a/core/java/android/bluetooth/BluetoothGattCallbackWrapper.java b/core/java/android/bluetooth/BluetoothGattCallbackWrapper.java
index 01778b3..17e533a 100644
--- a/core/java/android/bluetooth/BluetoothGattCallbackWrapper.java
+++ b/core/java/android/bluetooth/BluetoothGattCallbackWrapper.java
@@ -18,6 +18,7 @@
import android.bluetooth.le.AdvertiseSettings;
import android.bluetooth.le.ScanResult;
+import android.bluetooth.BluetoothGattService;
import android.os.ParcelUuid;
import android.os.RemoteException;
@@ -48,41 +49,17 @@
}
@Override
- public void onGetService(String address, int srvcType, int srvcInstId, ParcelUuid srvcUuid)
+ public void onSearchComplete(String address, List<BluetoothGattService> services,
+ int status) throws RemoteException {
+ }
+
+ @Override
+ public void onCharacteristicRead(String address, int status, int handle, byte[] value)
throws RemoteException {
}
@Override
- public void onGetIncludedService(String address, int srvcType, int srvcInstId,
- ParcelUuid srvcUuid, int inclSrvcType, int inclSrvcInstId, ParcelUuid inclSrvcUuid)
- throws RemoteException {
- }
-
- @Override
- public void onGetCharacteristic(String address, int srvcType, int srvcInstId,
- ParcelUuid srvcUuid, int charInstId, ParcelUuid charUuid, int charProps)
- throws RemoteException {
- }
-
- @Override
- public void onGetDescriptor(String address, int srvcType, int srvcInstId, ParcelUuid srvcUuid,
- int charInstId, ParcelUuid charUuid, int descrInstId, ParcelUuid descrUuid)
- throws RemoteException {
- }
-
- @Override
- public void onSearchComplete(String address, int status) throws RemoteException {
- }
-
- @Override
- public void onCharacteristicRead(String address, int status, int srvcType, int srvcInstId,
- ParcelUuid srvcUuid, int charInstId, ParcelUuid charUuid, byte[] value)
- throws RemoteException {
- }
-
- @Override
- public void onCharacteristicWrite(String address, int status, int srvcType, int srvcInstId,
- ParcelUuid srvcUuid, int charInstId, ParcelUuid charUuid) throws RemoteException {
+ public void onCharacteristicWrite(String address, int status, int handle) throws RemoteException {
}
@Override
@@ -90,20 +67,15 @@
}
@Override
- public void onDescriptorRead(String address, int status, int srvcType, int srvcInstId,
- ParcelUuid srvcUuid, int charInstId, ParcelUuid charUuid, int descrInstId,
- ParcelUuid descrUuid, byte[] value) throws RemoteException {
+ public void onDescriptorRead(String address, int status, int handle, byte[] value) throws RemoteException {
}
@Override
- public void onDescriptorWrite(String address, int status, int srvcType, int srvcInstId,
- ParcelUuid srvcUuid, int charInstId, ParcelUuid charUuid, int descrInstId,
- ParcelUuid descrUuid) throws RemoteException {
+ public void onDescriptorWrite(String address, int status, int handle) throws RemoteException {
}
@Override
- public void onNotify(String address, int srvcType, int srvcInstId, ParcelUuid srvcUuid,
- int charInstId, ParcelUuid charUuid, byte[] value) throws RemoteException {
+ public void onNotify(String address, int handle, byte[] value) throws RemoteException {
}
@Override
diff --git a/core/java/android/bluetooth/BluetoothGattCharacteristic.aidl b/core/java/android/bluetooth/BluetoothGattCharacteristic.aidl
new file mode 100644
index 0000000..bbb8623
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothGattCharacteristic.aidl
@@ -0,0 +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 android.bluetooth;
+
+parcelable BluetoothGattCharacteristic;
diff --git a/core/java/android/bluetooth/BluetoothGattCharacteristic.java b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
index 7cdcc2c..7d698b3 100644
--- a/core/java/android/bluetooth/BluetoothGattCharacteristic.java
+++ b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
@@ -15,6 +15,9 @@
*/
package android.bluetooth;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ParcelUuid;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@@ -26,7 +29,7 @@
* {@link BluetoothGattService}. The characteristic contains a value as well as
* additional information and optional GATT descriptors, {@link BluetoothGattDescriptor}.
*/
-public class BluetoothGattCharacteristic {
+public class BluetoothGattCharacteristic implements Parcelable {
/**
* Characteristic proprty: Characteristic is broadcastable.
@@ -242,6 +245,15 @@
initCharacteristic(service, uuid, instanceId, properties, permissions);
}
+ /**
+ * Create a new BluetoothGattCharacteristic
+ * @hide
+ */
+ public BluetoothGattCharacteristic(UUID uuid, int instanceId,
+ int properties, int permissions) {
+ initCharacteristic(null, uuid, instanceId, properties, permissions);
+ }
+
private void initCharacteristic(BluetoothGattService service,
UUID uuid, int instanceId,
int properties, int permissions) {
@@ -261,6 +273,50 @@
}
/**
+ * @hide
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeParcelable(new ParcelUuid(mUuid), 0);
+ out.writeInt(mInstance);
+ out.writeInt(mProperties);
+ out.writeInt(mPermissions);
+ out.writeTypedList(mDescriptors);
+ }
+
+ public static final Parcelable.Creator<BluetoothGattCharacteristic> CREATOR
+ = new Parcelable.Creator<BluetoothGattCharacteristic>() {
+ public BluetoothGattCharacteristic createFromParcel(Parcel in) {
+ return new BluetoothGattCharacteristic(in);
+ }
+
+ public BluetoothGattCharacteristic[] newArray(int size) {
+ return new BluetoothGattCharacteristic[size];
+ }
+ };
+
+ private BluetoothGattCharacteristic(Parcel in) {
+ mUuid = ((ParcelUuid)in.readParcelable(null)).getUuid();
+ mInstance = in.readInt();
+ mProperties = in.readInt();
+ mPermissions = in.readInt();
+
+ mDescriptors = new ArrayList<BluetoothGattDescriptor>();
+
+ ArrayList<BluetoothGattDescriptor> descs =
+ in.createTypedArrayList(BluetoothGattDescriptor.CREATOR);
+ if (descs != null) {
+ for (BluetoothGattDescriptor desc: descs) {
+ desc.setCharacteristic(this);
+ mDescriptors.add(desc);
+ }
+ }
+ }
+
+ /**
* Returns the deisred key size.
* @hide
*/
diff --git a/core/java/android/bluetooth/BluetoothGattDescriptor.aidl b/core/java/android/bluetooth/BluetoothGattDescriptor.aidl
new file mode 100644
index 0000000..4393273
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothGattDescriptor.aidl
@@ -0,0 +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 android.bluetooth;
+
+parcelable BluetoothGattDescriptor;
diff --git a/core/java/android/bluetooth/BluetoothGattDescriptor.java b/core/java/android/bluetooth/BluetoothGattDescriptor.java
index 5f525dc..28317c4 100644
--- a/core/java/android/bluetooth/BluetoothGattDescriptor.java
+++ b/core/java/android/bluetooth/BluetoothGattDescriptor.java
@@ -16,6 +16,9 @@
package android.bluetooth;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ParcelUuid;
import java.util.UUID;
/**
@@ -25,7 +28,7 @@
* characteristic, {@link BluetoothGattCharacteristic}. They can be used to describe
* the characteristic's features or to control certain behaviours of the characteristic.
*/
-public class BluetoothGattDescriptor {
+public class BluetoothGattDescriptor implements Parcelable {
/**
* Value used to enable notification for a client configuration descriptor
@@ -138,6 +141,13 @@
initDescriptor(characteristic, uuid, instance, permissions);
}
+ /**
+ * @hide
+ */
+ public BluetoothGattDescriptor(UUID uuid, int instance, int permissions) {
+ initDescriptor(null, uuid, instance, permissions);
+ }
+
private void initDescriptor(BluetoothGattCharacteristic characteristic, UUID uuid,
int instance, int permissions) {
mCharacteristic = characteristic;
@@ -147,6 +157,36 @@
}
/**
+ * @hide
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeParcelable(new ParcelUuid(mUuid), 0);
+ out.writeInt(mInstance);
+ out.writeInt(mPermissions);
+ }
+
+ public static final Parcelable.Creator<BluetoothGattDescriptor> CREATOR
+ = new Parcelable.Creator<BluetoothGattDescriptor>() {
+ public BluetoothGattDescriptor createFromParcel(Parcel in) {
+ return new BluetoothGattDescriptor(in);
+ }
+
+ public BluetoothGattDescriptor[] newArray(int size) {
+ return new BluetoothGattDescriptor[size];
+ }
+ };
+
+ private BluetoothGattDescriptor(Parcel in) {
+ mUuid = ((ParcelUuid)in.readParcelable(null)).getUuid();
+ mInstance = in.readInt();
+ mPermissions = in.readInt();
+ }
+
+ /**
* Returns the characteristic this descriptor belongs to.
* @return The characteristic.
*/
diff --git a/core/java/android/bluetooth/BluetoothGattIncludedService.aidl b/core/java/android/bluetooth/BluetoothGattIncludedService.aidl
new file mode 100644
index 0000000..1ef427e
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothGattIncludedService.aidl
@@ -0,0 +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 android.bluetooth;
+
+parcelable BluetoothGattIncludedService;
diff --git a/core/java/android/bluetooth/BluetoothGattIncludedService.java b/core/java/android/bluetooth/BluetoothGattIncludedService.java
new file mode 100644
index 0000000..155dc57
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothGattIncludedService.java
@@ -0,0 +1,110 @@
+/*
+ * 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.bluetooth;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ParcelUuid;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Represents a Bluetooth GATT Included Service
+ * @hide
+ */
+public class BluetoothGattIncludedService implements Parcelable {
+
+ /**
+ * The UUID of this service.
+ */
+ protected UUID mUuid;
+
+ /**
+ * Instance ID for this service.
+ */
+ protected int mInstanceId;
+
+ /**
+ * Service type (Primary/Secondary).
+ */
+ protected int mServiceType;
+
+ /**
+ * Create a new BluetoothGattIncludedService
+ */
+ public BluetoothGattIncludedService(UUID uuid, int instanceId, int serviceType) {
+ mUuid = uuid;
+ mInstanceId = instanceId;
+ mServiceType = serviceType;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeParcelable(new ParcelUuid(mUuid), 0);
+ out.writeInt(mInstanceId);
+ out.writeInt(mServiceType);
+ }
+
+ public static final Parcelable.Creator<BluetoothGattIncludedService> CREATOR
+ = new Parcelable.Creator<BluetoothGattIncludedService>() {
+ public BluetoothGattIncludedService createFromParcel(Parcel in) {
+ return new BluetoothGattIncludedService(in);
+ }
+
+ public BluetoothGattIncludedService[] newArray(int size) {
+ return new BluetoothGattIncludedService[size];
+ }
+ };
+
+ private BluetoothGattIncludedService(Parcel in) {
+ mUuid = ((ParcelUuid)in.readParcelable(null)).getUuid();
+ mInstanceId = in.readInt();
+ mServiceType = in.readInt();
+ }
+
+ /**
+ * Returns the UUID of this service
+ *
+ * @return UUID of this service
+ */
+ public UUID getUuid() {
+ return mUuid;
+ }
+
+ /**
+ * Returns the instance ID for this service
+ *
+ * <p>If a remote device offers multiple services with the same UUID
+ * (ex. multiple battery services for different batteries), the instance
+ * ID is used to distuinguish services.
+ *
+ * @return Instance ID of this service
+ */
+ public int getInstanceId() {
+ return mInstanceId;
+ }
+
+ /**
+ * Get the type of this service (primary/secondary)
+ */
+ public int getType() {
+ return mServiceType;
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothGattService.aidl b/core/java/android/bluetooth/BluetoothGattService.aidl
new file mode 100644
index 0000000..84314d2
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothGattService.aidl
@@ -0,0 +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 android.bluetooth;
+
+parcelable BluetoothGattService;
diff --git a/core/java/android/bluetooth/BluetoothGattService.java b/core/java/android/bluetooth/BluetoothGattService.java
index 52bc0f7..a4e1dc0 100644
--- a/core/java/android/bluetooth/BluetoothGattService.java
+++ b/core/java/android/bluetooth/BluetoothGattService.java
@@ -15,6 +15,9 @@
*/
package android.bluetooth;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ParcelUuid;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@@ -25,7 +28,7 @@
* <p> Gatt Service contains a collection of {@link BluetoothGattCharacteristic},
* as well as referenced services.
*/
-public class BluetoothGattService {
+public class BluetoothGattService implements Parcelable {
/**
* Primary service
@@ -117,6 +120,81 @@
}
/**
+ * Create a new BluetoothGattService
+ * @hide
+ */
+ public BluetoothGattService(UUID uuid, int instanceId, int serviceType) {
+ mDevice = null;
+ mUuid = uuid;
+ mInstanceId = instanceId;
+ mServiceType = serviceType;
+ mCharacteristics = new ArrayList<BluetoothGattCharacteristic>();
+ mIncludedServices = new ArrayList<BluetoothGattService>();
+ }
+
+ /**
+ * @hide
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeParcelable(new ParcelUuid(mUuid), 0);
+ out.writeInt(mInstanceId);
+ out.writeInt(mServiceType);
+ out.writeTypedList(mCharacteristics);
+
+ ArrayList<BluetoothGattIncludedService> includedServices =
+ new ArrayList<BluetoothGattIncludedService>(mIncludedServices.size());
+ for(BluetoothGattService s : mIncludedServices) {
+ includedServices.add(new BluetoothGattIncludedService(s.getUuid(),
+ s.getInstanceId(), s.getType()));
+ }
+ out.writeTypedList(includedServices);
+ }
+
+ public static final Parcelable.Creator<BluetoothGattService> CREATOR
+ = new Parcelable.Creator<BluetoothGattService>() {
+ public BluetoothGattService createFromParcel(Parcel in) {
+ return new BluetoothGattService(in);
+ }
+
+ public BluetoothGattService[] newArray(int size) {
+ return new BluetoothGattService[size];
+ }
+ };
+
+ private BluetoothGattService(Parcel in) {
+ mUuid = ((ParcelUuid)in.readParcelable(null)).getUuid();
+ mInstanceId = in.readInt();
+ mServiceType = in.readInt();
+
+ mCharacteristics = new ArrayList<BluetoothGattCharacteristic>();
+
+ ArrayList<BluetoothGattCharacteristic> chrcs =
+ in.createTypedArrayList(BluetoothGattCharacteristic.CREATOR);
+ if (chrcs != null) {
+ for (BluetoothGattCharacteristic chrc : chrcs) {
+ chrc.setService(this);
+ mCharacteristics.add(chrc);
+ }
+ }
+
+ mIncludedServices = new ArrayList<BluetoothGattService>();
+
+ ArrayList<BluetoothGattIncludedService> inclSvcs =
+ in.createTypedArrayList(BluetoothGattIncludedService.CREATOR);
+ if (chrcs != null) {
+ for (BluetoothGattIncludedService isvc : inclSvcs) {
+ mIncludedServices.add(new BluetoothGattService(null, isvc.getUuid(),
+ isvc.getInstanceId(), isvc.getType()));
+ }
+ }
+ }
+
+ /**
* Returns the device associated with this service.
* @hide
*/
@@ -125,6 +203,14 @@
}
/**
+ * Returns the device associated with this service.
+ * @hide
+ */
+ /*package*/ void setDevice(BluetoothDevice device) {
+ this.mDevice = device;
+ }
+
+ /**
* Add an included service to this service.
* <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
*
@@ -192,7 +278,7 @@
* Add an included service to the internal map.
* @hide
*/
- /*package*/ void addIncludedService(BluetoothGattService includedService) {
+ public void addIncludedService(BluetoothGattService includedService) {
mIncludedServices.add(includedService);
}
diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl
index 6b5f77f..adb07df 100644
--- a/core/java/android/bluetooth/IBluetoothGatt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGatt.aidl
@@ -51,28 +51,13 @@
void clientDisconnect(in int clientIf, in String address);
void refreshDevice(in int clientIf, in String address);
void discoverServices(in int clientIf, in String address);
- void readCharacteristic(in int clientIf, in String address, in int srvcType,
- in int srvcInstanceId, in ParcelUuid srvcId,
- in int charInstanceId, in ParcelUuid charId,
- in int authReq);
- void writeCharacteristic(in int clientIf, in String address, in int srvcType,
- in int srvcInstanceId, in ParcelUuid srvcId,
- in int charInstanceId, in ParcelUuid charId,
+ void readCharacteristic(in int clientIf, in String address, in int handle, in int authReq);
+ void writeCharacteristic(in int clientIf, in String address, in int handle,
in int writeType, in int authReq, in byte[] value);
- void readDescriptor(in int clientIf, in String address, in int srvcType,
- in int srvcInstanceId, in ParcelUuid srvcId,
- in int charInstanceId, in ParcelUuid charId,
- in int descrInstanceId, in ParcelUuid descrUuid,
- in int authReq);
- void writeDescriptor(in int clientIf, in String address, in int srvcType,
- in int srvcInstanceId, in ParcelUuid srvcId,
- in int charInstanceId, in ParcelUuid charId,
- in int descrInstanceId, in ParcelUuid descrId,
+ void readDescriptor(in int clientIf, in String address, in int handle, in int authReq);
+ void writeDescriptor(in int clientIf, in String address, in int handle,
in int writeType, in int authReq, in byte[] value);
- void registerForNotification(in int clientIf, in String address, in int srvcType,
- in int srvcInstanceId, in ParcelUuid srvcId,
- in int charInstanceId, in ParcelUuid charId,
- in boolean enable);
+ void registerForNotification(in int clientIf, in String address, in int handle, in boolean enable);
void beginReliableWrite(in int clientIf, in String address);
void endReliableWrite(in int clientIf, in String address, in boolean execute);
void readRemoteRssi(in int clientIf, in String address);
diff --git a/core/java/android/bluetooth/IBluetoothGattCallback.aidl b/core/java/android/bluetooth/IBluetoothGattCallback.aidl
index cbba9f0..7163c37 100644
--- a/core/java/android/bluetooth/IBluetoothGattCallback.aidl
+++ b/core/java/android/bluetooth/IBluetoothGattCallback.aidl
@@ -16,6 +16,7 @@
package android.bluetooth;
import android.os.ParcelUuid;
+import android.bluetooth.BluetoothGattService;
import android.bluetooth.le.AdvertiseSettings;
import android.bluetooth.le.ScanResult;
@@ -29,41 +30,13 @@
in boolean connected, in String address);
void onScanResult(in ScanResult scanResult);
void onBatchScanResults(in List<ScanResult> batchResults);
- void onGetService(in String address, in int srvcType, in int srvcInstId,
- in ParcelUuid srvcUuid);
- void onGetIncludedService(in String address, in int srvcType, in int srvcInstId,
- in ParcelUuid srvcUuid, in int inclSrvcType,
- in int inclSrvcInstId, in ParcelUuid inclSrvcUuid);
- void onGetCharacteristic(in String address, in int srvcType,
- in int srvcInstId, in ParcelUuid srvcUuid,
- in int charInstId, in ParcelUuid charUuid,
- in int charProps);
- void onGetDescriptor(in String address, in int srvcType,
- in int srvcInstId, in ParcelUuid srvcUuid,
- in int charInstId, in ParcelUuid charUuid,
- in int descrInstId, in ParcelUuid descrUuid);
- void onSearchComplete(in String address, in int status);
- void onCharacteristicRead(in String address, in int status, in int srvcType,
- in int srvcInstId, in ParcelUuid srvcUuid,
- in int charInstId, in ParcelUuid charUuid,
- in byte[] value);
- void onCharacteristicWrite(in String address, in int status, in int srvcType,
- in int srvcInstId, in ParcelUuid srvcUuid,
- in int charInstId, in ParcelUuid charUuid);
+ void onSearchComplete(in String address, in List<BluetoothGattService> services, in int status);
+ void onCharacteristicRead(in String address, in int status, in int handle, in byte[] value);
+ void onCharacteristicWrite(in String address, in int status, in int handle);
void onExecuteWrite(in String address, in int status);
- void onDescriptorRead(in String address, in int status, in int srvcType,
- in int srvcInstId, in ParcelUuid srvcUuid,
- in int charInstId, in ParcelUuid charUuid,
- in int descrInstId, in ParcelUuid descrUuid,
- in byte[] value);
- void onDescriptorWrite(in String address, in int status, in int srvcType,
- in int srvcInstId, in ParcelUuid srvcUuid,
- in int charInstId, in ParcelUuid charUuid,
- in int descrInstId, in ParcelUuid descrUuid);
- void onNotify(in String address, in int srvcType,
- in int srvcInstId, in ParcelUuid srvcUuid,
- in int charInstId, in ParcelUuid charUuid,
- in byte[] value);
+ void onDescriptorRead(in String address, in int status, in int handle, in byte[] value);
+ void onDescriptorWrite(in String address, in int status, in int handle);
+ void onNotify(in String address, in int handle, in byte[] value);
void onReadRemoteRssi(in String address, in int rssi, in int status);
void onMultiAdvertiseCallback(in int status, boolean isStart,
in AdvertiseSettings advertiseSettings);
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 3792e5c..f9a7d19 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -54,6 +54,8 @@
* This is a sticky broadcast for clients that includes USB connected/disconnected state,
* <ul>
* <li> {@link #USB_CONNECTED} boolean indicating whether USB is connected or disconnected.
+ * <li> {@link #USB_HOST_CONNECTED} boolean indicating whether USB is connected or
+ * disconnected as host.
* <li> {@link #USB_CONFIGURED} boolean indicating whether USB is configured.
* currently zero if not configured, one for configured.
* <li> {@link #USB_FUNCTION_ADB} boolean extra indicating whether the
@@ -152,6 +154,14 @@
public static final String USB_CONNECTED = "connected";
/**
+ * Boolean extra indicating whether USB is connected or disconnected as host.
+ * Used in extras for the {@link #ACTION_USB_STATE} broadcast.
+ *
+ * {@hide}
+ */
+ public static final String USB_HOST_CONNECTED = "host_connected";
+
+ /**
* Boolean extra indicating whether USB is configured.
* Used in extras for the {@link #ACTION_USB_STATE} broadcast.
*
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 5f1043b..f0673ff 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -2744,7 +2744,9 @@
if (networkCallback == null) {
throw new IllegalArgumentException("null NetworkCallback");
}
- if (need == null) throw new IllegalArgumentException("null NetworkCapabilities");
+ if (need == null && action != REQUEST) {
+ throw new IllegalArgumentException("null NetworkCapabilities");
+ }
try {
incCallbackHandlerRefCount();
synchronized(sNetworkCallback) {
@@ -2767,7 +2769,7 @@
}
/**
- * Helper function to requests a network with a particular legacy type.
+ * Helper function to request a network with a particular legacy type.
*
* This is temporarily public @hide so it can be called by system code that uses the
* NetworkRequest API to request networks but relies on CONNECTIVITY_ACTION broadcasts for
@@ -3011,6 +3013,28 @@
}
/**
+ * Registers to receive notifications about whichever network currently satisfies the
+ * system default {@link NetworkRequest}. The callbacks will continue to be called until
+ * either the application exits or {@link #unregisterNetworkCallback} is called
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ *
+ * @param networkCallback The {@link NetworkCallback} that the system will call as the
+ * system default network changes.
+ * @hide
+ */
+ public void registerDefaultNetworkCallback(NetworkCallback networkCallback) {
+ // This works because if the NetworkCapabilities are null,
+ // ConnectivityService takes them from the default request.
+ //
+ // Since the capabilities are exactly the same as the default request's
+ // capabilities, this request is guaranteed, at all times, to be
+ // satisfied by the same network, if any, that satisfies the default
+ // request, i.e., the system default network.
+ sendRequestForNetwork(null, networkCallback, 0, REQUEST, TYPE_NONE);
+ }
+
+ /**
* Requests bandwidth update for a given {@link Network} and returns whether the update request
* is accepted by ConnectivityService. Once accepted, ConnectivityService will poll underlying
* network connection for updated bandwidth information. The caller will be notified via
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index 5761d66..d847cd0 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -250,6 +250,16 @@
}
}
+ public boolean isPersistable() {
+ switch (mMatchRule) {
+ case MATCH_MOBILE_WILDCARD:
+ case MATCH_WIFI_WILDCARD:
+ return false;
+ default:
+ return true;
+ }
+ }
+
public int getMatchRule() {
return mMatchRule;
}
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index 555032d..141af3d 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -39,23 +39,6 @@
private static final String TAG = "NetworkUtils";
- /** Setting bit 0 indicates reseting of IPv4 addresses required */
- public static final int RESET_IPV4_ADDRESSES = 0x01;
-
- /** Setting bit 1 indicates reseting of IPv4 addresses required */
- public static final int RESET_IPV6_ADDRESSES = 0x02;
-
- /** Reset all addresses */
- public static final int RESET_ALL_ADDRESSES = RESET_IPV4_ADDRESSES | RESET_IPV6_ADDRESSES;
-
- /**
- * Reset IPv6 or IPv4 sockets that are connected via the named interface.
- *
- * @param interfaceName is the interface to reset
- * @param mask {@see #RESET_IPV4_ADDRESSES} and {@see #RESET_IPV6_ADDRESSES}
- */
- public native static int resetConnections(String interfaceName, int mask);
-
/**
* Attaches a socket filter that accepts DHCP packets to the given socket.
*/
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index 95ffb44..cfd0468 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -147,8 +147,10 @@
}
/**
- * System API for backup-related support components to tag network traffic
- * appropriately.
+ * Set active tag to use when accounting {@link Socket} traffic originating
+ * from the current thread. The tag used internally is well-defined to
+ * distinguish all backup-related traffic.
+ *
* @hide
*/
@SystemApi
@@ -157,8 +159,10 @@
}
/**
- * System API for restore-related support components to tag network traffic
- * appropriately.
+ * Set active tag to use when accounting {@link Socket} traffic originating
+ * from the current thread. The tag used internally is well-defined to
+ * distinguish all restore-related traffic.
+ *
* @hide
*/
@SystemApi
@@ -205,7 +209,13 @@
NetworkManagementSocketTagger.setThreadSocketStatsUid(uid);
}
- /** {@hide} */
+ /**
+ * Clear any active UID set to account {@link Socket} traffic originating
+ * from the current thread.
+ *
+ * @see #setThreadStatsUid(int)
+ * @hide
+ */
@SystemApi
public static void clearThreadStatsUid() {
NetworkManagementSocketTagger.setThreadSocketStatsUid(-1);
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index f382241..55b107a 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -19,8 +19,11 @@
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.TypedProperties;
+import android.app.AppGlobals;
+import android.content.Context;
import android.util.Log;
+import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
@@ -100,14 +103,6 @@
*/
private static final String DEFAULT_TRACE_BODY = "dmtrace";
private static final String DEFAULT_TRACE_EXTENSION = ".trace";
- private static class NoPreloadHolder {
- private static final String DEFAULT_TRACE_PATH_PREFIX =
- Environment.getLegacyExternalStorageDirectory().getPath() + "/";
- private static final String DEFAULT_TRACE_FILE_PATH =
- DEFAULT_TRACE_PATH_PREFIX + DEFAULT_TRACE_BODY
- + DEFAULT_TRACE_EXTENSION;
- }
-
/**
* This class is used to retrieved various statistics about the memory mappings for this
@@ -938,109 +933,171 @@
}
/**
- * Start method tracing with default log name and buffer size. See <a
-href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Log Viewer</a> for
- * information about reading these files. Call stopMethodTracing() to stop
- * tracing.
+ * Start method tracing with default log name and buffer size.
+ * <p>
+ * By default, the trace file is called "dmtrace.trace" and it's placed
+ * under your package-specific directory on primary shared/external storage,
+ * as returned by {@link Context#getExternalFilesDir(String)}.
+ * <p>
+ * See <a href="{@docRoot}guide/developing/tools/traceview.html">Traceview:
+ * A Graphical Log Viewer</a> for information about reading trace files.
+ * <p class="note">
+ * When method tracing is enabled, the VM will run more slowly than usual,
+ * so the timings from the trace files should only be considered in relative
+ * terms (e.g. was run #1 faster than run #2). The times for native methods
+ * will not change, so don't try to use this to compare the performance of
+ * interpreted and native implementations of the same method. As an
+ * alternative, consider using sampling-based method tracing via
+ * {@link #startMethodTracingSampling(String, int, int)} or "native" tracing
+ * in the emulator via {@link #startNativeTracing()}.
+ * </p>
*/
public static void startMethodTracing() {
- VMDebug.startMethodTracing(fixTraceName(null), 0, 0, false, 0);
+ VMDebug.startMethodTracing(fixTracePath(null), 0, 0, false, 0);
}
/**
- * Start method tracing, specifying the trace log file name. The trace
- * file will be put under "/sdcard" unless an absolute path is given.
- * See <a
- href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Log Viewer</a> for
- * information about reading trace files.
- *
- * @param traceName Name for the trace log file to create.
- * If {@code traceName} is null, this value defaults to "/sdcard/dmtrace.trace".
- * If the files already exist, they will be truncated.
- * If the trace file given does not end in ".trace", it will be appended for you.
- */
- public static void startMethodTracing(String traceName) {
- startMethodTracing(traceName, 0, 0);
- }
-
- /**
- * Start method tracing, specifying the trace log file name and the
- * buffer size. The trace files will be put under "/sdcard" unless an
- * absolute path is given. See <a
- href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Log Viewer</a> for
- * information about reading trace files.
- * @param traceName Name for the trace log file to create.
- * If {@code traceName} is null, this value defaults to "/sdcard/dmtrace.trace".
- * If the files already exist, they will be truncated.
- * If the trace file given does not end in ".trace", it will be appended for you.
- *
- * @param bufferSize The maximum amount of trace data we gather. If not given, it defaults to 8MB.
- */
- public static void startMethodTracing(String traceName, int bufferSize) {
- startMethodTracing(traceName, bufferSize, 0);
- }
-
- /**
- * Start method tracing, specifying the trace log file name and the
- * buffer size. The trace files will be put under "/sdcard" unless an
- * absolute path is given. See <a
- href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Log Viewer</a> for
- * information about reading trace files.
- *
+ * Start method tracing, specifying the trace log file path.
* <p>
- * When method tracing is enabled, the VM will run more slowly than
- * usual, so the timings from the trace files should only be considered
- * in relative terms (e.g. was run #1 faster than run #2). The times
- * for native methods will not change, so don't try to use this to
- * compare the performance of interpreted and native implementations of the
- * same method. As an alternative, consider using sampling-based method
- * tracing via {@link #startMethodTracingSampling(String, int, int)} or
- * "native" tracing in the emulator via {@link #startNativeTracing()}.
+ * When a relative file path is given, the trace file will be placed under
+ * your package-specific directory on primary shared/external storage, as
+ * returned by {@link Context#getExternalFilesDir(String)}.
+ * <p>
+ * See <a href="{@docRoot}guide/developing/tools/traceview.html">Traceview:
+ * A Graphical Log Viewer</a> for information about reading trace files.
+ * <p class="note">
+ * When method tracing is enabled, the VM will run more slowly than usual,
+ * so the timings from the trace files should only be considered in relative
+ * terms (e.g. was run #1 faster than run #2). The times for native methods
+ * will not change, so don't try to use this to compare the performance of
+ * interpreted and native implementations of the same method. As an
+ * alternative, consider using sampling-based method tracing via
+ * {@link #startMethodTracingSampling(String, int, int)} or "native" tracing
+ * in the emulator via {@link #startNativeTracing()}.
* </p>
*
- * @param traceName Name for the trace log file to create.
- * If {@code traceName} is null, this value defaults to "/sdcard/dmtrace.trace".
- * If the files already exist, they will be truncated.
- * If the trace file given does not end in ".trace", it will be appended for you.
- * @param bufferSize The maximum amount of trace data we gather. If not given, it defaults to 8MB.
- * @param flags Flags to control method tracing. The only one that is currently defined is {@link #TRACE_COUNT_ALLOCS}.
+ * @param tracePath Path to the trace log file to create. If {@code null},
+ * this will default to "dmtrace.trace". If the file already
+ * exists, it will be truncated. If the path given does not end
+ * in ".trace", it will be appended for you.
*/
- public static void startMethodTracing(String traceName, int bufferSize,
- int flags) {
- VMDebug.startMethodTracing(fixTraceName(traceName), bufferSize, flags, false, 0);
+ public static void startMethodTracing(String tracePath) {
+ startMethodTracing(tracePath, 0, 0);
+ }
+
+ /**
+ * Start method tracing, specifying the trace log file name and the buffer
+ * size.
+ * <p>
+ * When a relative file path is given, the trace file will be placed under
+ * your package-specific directory on primary shared/external storage, as
+ * returned by {@link Context#getExternalFilesDir(String)}.
+ * <p>
+ * See <a href="{@docRoot}guide/developing/tools/traceview.html">Traceview:
+ * A Graphical Log Viewer</a> for information about reading trace files.
+ * <p class="note">
+ * When method tracing is enabled, the VM will run more slowly than usual,
+ * so the timings from the trace files should only be considered in relative
+ * terms (e.g. was run #1 faster than run #2). The times for native methods
+ * will not change, so don't try to use this to compare the performance of
+ * interpreted and native implementations of the same method. As an
+ * alternative, consider using sampling-based method tracing via
+ * {@link #startMethodTracingSampling(String, int, int)} or "native" tracing
+ * in the emulator via {@link #startNativeTracing()}.
+ * </p>
+ *
+ * @param tracePath Path to the trace log file to create. If {@code null},
+ * this will default to "dmtrace.trace". If the file already
+ * exists, it will be truncated. If the path given does not end
+ * in ".trace", it will be appended for you.
+ * @param bufferSize The maximum amount of trace data we gather. If not
+ * given, it defaults to 8MB.
+ */
+ public static void startMethodTracing(String tracePath, int bufferSize) {
+ startMethodTracing(tracePath, bufferSize, 0);
+ }
+
+ /**
+ * Start method tracing, specifying the trace log file name, the buffer
+ * size, and flags.
+ * <p>
+ * When a relative file path is given, the trace file will be placed under
+ * your package-specific directory on primary shared/external storage, as
+ * returned by {@link Context#getExternalFilesDir(String)}.
+ * <p>
+ * See <a href="{@docRoot}guide/developing/tools/traceview.html">Traceview:
+ * A Graphical Log Viewer</a> for information about reading trace files.
+ * <p class="note">
+ * When method tracing is enabled, the VM will run more slowly than usual,
+ * so the timings from the trace files should only be considered in relative
+ * terms (e.g. was run #1 faster than run #2). The times for native methods
+ * will not change, so don't try to use this to compare the performance of
+ * interpreted and native implementations of the same method. As an
+ * alternative, consider using sampling-based method tracing via
+ * {@link #startMethodTracingSampling(String, int, int)} or "native" tracing
+ * in the emulator via {@link #startNativeTracing()}.
+ * </p>
+ *
+ * @param tracePath Path to the trace log file to create. If {@code null},
+ * this will default to "dmtrace.trace". If the file already
+ * exists, it will be truncated. If the path given does not end
+ * in ".trace", it will be appended for you.
+ * @param bufferSize The maximum amount of trace data we gather. If not
+ * given, it defaults to 8MB.
+ * @param flags Flags to control method tracing. The only one that is
+ * currently defined is {@link #TRACE_COUNT_ALLOCS}.
+ */
+ public static void startMethodTracing(String tracePath, int bufferSize, int flags) {
+ VMDebug.startMethodTracing(fixTracePath(tracePath), bufferSize, flags, false, 0);
}
/**
* Start sampling-based method tracing, specifying the trace log file name,
- * the buffer size, and the sampling interval. The trace files will be put
- * under "/sdcard" unless an absolute path is given. See <a
- href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Log Viewer</a>
- * for information about reading trace files.
+ * the buffer size, and the sampling interval.
+ * <p>
+ * When a relative file path is given, the trace file will be placed under
+ * your package-specific directory on primary shared/external storage, as
+ * returned by {@link Context#getExternalFilesDir(String)}.
+ * <p>
+ * See <a href="{@docRoot}guide/developing/tools/traceview.html">Traceview:
+ * A Graphical Log Viewer</a> for information about reading trace files.
*
- * @param traceName Name for the trace log file to create.
- * If {@code traceName} is null, this value defaults to "/sdcard/dmtrace.trace".
- * If the files already exist, they will be truncated.
- * If the trace file given does not end in ".trace", it will be appended for you.
- * @param bufferSize The maximum amount of trace data we gather. If not given, it defaults to 8MB.
- * @param intervalUs The amount of time between each sample in microseconds.
+ * @param tracePath Path to the trace log file to create. If {@code null},
+ * this will default to "dmtrace.trace". If the file already
+ * exists, it will be truncated. If the path given does not end
+ * in ".trace", it will be appended for you.
+ * @param bufferSize The maximum amount of trace data we gather. If not
+ * given, it defaults to 8MB.
+ * @param intervalUs The amount of time between each sample in microseconds.
*/
- public static void startMethodTracingSampling(String traceName,
- int bufferSize, int intervalUs) {
- VMDebug.startMethodTracing(fixTraceName(traceName), bufferSize, 0, true, intervalUs);
+ public static void startMethodTracingSampling(String tracePath, int bufferSize,
+ int intervalUs) {
+ VMDebug.startMethodTracing(fixTracePath(tracePath), bufferSize, 0, true, intervalUs);
}
-
+
/**
* Formats name of trace log file for method tracing.
*/
- private static String fixTraceName(String traceName) {
- if (traceName == null)
- traceName = NoPreloadHolder.DEFAULT_TRACE_FILE_PATH;
- if (traceName.charAt(0) != '/')
- traceName = NoPreloadHolder.DEFAULT_TRACE_PATH_PREFIX + traceName;
- if (!traceName.endsWith(DEFAULT_TRACE_EXTENSION))
- traceName = traceName + DEFAULT_TRACE_EXTENSION;
+ private static String fixTracePath(String tracePath) {
+ if (tracePath == null || tracePath.charAt(0) != '/') {
+ final Context context = AppGlobals.getInitialApplication();
+ final File dir;
+ if (context != null) {
+ dir = context.getExternalFilesDir(null);
+ } else {
+ dir = Environment.getExternalStorageDirectory();
+ }
- return traceName;
+ if (tracePath == null) {
+ tracePath = new File(dir, DEFAULT_TRACE_BODY).getAbsolutePath();
+ } else {
+ tracePath = new File(dir, tracePath).getAbsolutePath();
+ }
+ }
+ if (!tracePath.endsWith(DEFAULT_TRACE_EXTENSION)) {
+ tracePath += DEFAULT_TRACE_EXTENSION;
+ }
+ return tracePath;
}
/**
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index cd84c8f..100f6a1 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -97,12 +97,6 @@
void setInterfaceIpv6NdOffload(String iface, boolean enable);
/**
- * Retrieves the network routes currently configured on the specified
- * interface
- */
- RouteInfo[] getRoutes(String iface);
-
- /**
* Add the specified route to the interface.
*/
void addRoute(int netId, in RouteInfo route);
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index bc2566b..c38bf3c 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -76,4 +76,5 @@
PersistableBundle getSeedAccountOptions();
void clearSeedAccountData();
boolean someUserHasSeedAccount(in String accountName, in String accountType);
+ boolean isManagedProfile(int userId);
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 0ff0154..707d5f5 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -822,8 +822,28 @@
*/
@SystemApi
public boolean isManagedProfile() {
- UserInfo user = getUserInfo(UserHandle.myUserId());
- return user != null ? user.isManagedProfile() : false;
+ try {
+ return mService.isManagedProfile(UserHandle.myUserId());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Checks if the specified user is a managed profile.
+ * Requires {@link android.Manifest.permission#MANAGE_USERS} permission, otherwise the caller
+ * must be in the same profile group of specified user.
+ *
+ * @return whether the specified user is a managed profile.
+ * @hide
+ */
+ @SystemApi
+ public boolean isManagedProfile(@UserIdInt int userId) {
+ try {
+ return mService.isManagedProfile(userId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
}
/**
@@ -964,8 +984,7 @@
/**
* Returns the UserInfo object describing a specific user.
- * Requires {@link android.Manifest.permission#MANAGE_USERS} permission or the caller is
- * in the same profile group of target user.
+ * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
* @param userHandle the user handle of the user whose information is being requested.
* @return the UserInfo object for a specific user.
* @hide
diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
index ea0597d..4b70649 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -438,6 +438,10 @@
final Intent intent = new Intent(DocumentsContract.ACTION_BROWSE);
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setData(uri);
+
+ // note that docsui treats this as *force* show advanced. So sending
+ // false permits advanced to be shown based on user preferences.
+ intent.putExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, isPrimary());
intent.putExtra(DocumentsContract.EXTRA_SHOW_FILESIZE, true);
return intent;
}
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 4ad7969..4412459 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -93,6 +93,9 @@
public static final String EXTRA_PACKAGE_NAME = "android.content.extra.PACKAGE_NAME";
/** {@hide} */
+ public static final String EXTRA_SHOW_ADVANCED = "android.content.extra.SHOW_ADVANCED";
+
+ /** {@hide} */
public static final String EXTRA_SHOW_FILESIZE = "android.content.extra.SHOW_FILESIZE";
/** {@hide} */
@@ -556,13 +559,22 @@
public static final int FLAG_EMPTY = 1 << 16;
/**
+ * Flag indicating that this root should only be visible to advanced
+ * users.
+ *
+ * @see #COLUMN_FLAGS
+ * @hide
+ */
+ public static final int FLAG_ADVANCED = 1 << 17;
+
+ /**
* Flag indicating that this root has settings.
*
* @see #COLUMN_FLAGS
* @see DocumentsContract#ACTION_DOCUMENT_ROOT_SETTINGS
* @hide
*/
- public static final int FLAG_HAS_SETTINGS = 1 << 17;
+ public static final int FLAG_HAS_SETTINGS = 1 << 18;
/**
* Flag indicating that this root is on removable SD card storage.
@@ -570,7 +582,7 @@
* @see #COLUMN_FLAGS
* @hide
*/
- public static final int FLAG_REMOVABLE_SD = 1 << 18;
+ public static final int FLAG_REMOVABLE_SD = 1 << 19;
/**
* Flag indicating that this root is on removable USB storage.
@@ -578,7 +590,7 @@
* @see #COLUMN_FLAGS
* @hide
*/
- public static final int FLAG_REMOVABLE_USB = 1 << 19;
+ public static final int FLAG_REMOVABLE_USB = 1 << 20;
}
/**
diff --git a/core/java/android/provider/UserDictionary.java b/core/java/android/provider/UserDictionary.java
index a9b106a..c6e58cb 100644
--- a/core/java/android/provider/UserDictionary.java
+++ b/core/java/android/provider/UserDictionary.java
@@ -28,6 +28,9 @@
* A provider of user defined words for input methods to use for predictive text input.
* Applications and input methods may add words into the dictionary. Words can have associated
* frequency information and locale information.
+ *
+ * <p><strong>NOTE: </strong>Starting on API 23, the user dictionary is only accessible through
+ * IME and spellchecker.
*/
public class UserDictionary {
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
index dcf987b..d9227ce 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
@@ -16,17 +16,20 @@
package android.util.apk;
+import android.system.ErrnoException;
+import android.system.OsConstants;
+import android.util.ArrayMap;
import android.util.Pair;
import java.io.ByteArrayInputStream;
+import java.io.FileDescriptor;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.math.BigInteger;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
-import java.nio.MappedByteBuffer;
-import java.nio.channels.FileChannel;
+import java.nio.DirectByteBuffer;
import java.security.DigestException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
@@ -52,11 +55,13 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import libcore.io.Libcore;
+import libcore.io.Os;
+
/**
* APK Signature Scheme v2 verifier.
*
@@ -75,44 +80,17 @@
public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 2;
/**
- * Returns {@code true} if the provided APK contains an APK Signature Scheme V2
- * signature. The signature will not be verified.
+ * Returns {@code true} if the provided APK contains an APK Signature Scheme V2 signature.
+ *
+ * <p><b>NOTE: This method does not verify the signature.</b>
*/
public static boolean hasSignature(String apkFile) throws IOException {
try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
- long fileSize = apk.length();
- if (fileSize > Integer.MAX_VALUE) {
- return false;
- }
- MappedByteBuffer apkContents;
- try {
- apkContents = apk.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, fileSize);
- } catch (IOException e) {
- if (e.getCause() instanceof OutOfMemoryError) {
- // TODO: Remove this temporary workaround once verifying large APKs is
- // supported. Very large APKs cannot be memory-mapped. This verification code
- // needs to change to use a different approach for verifying such APKs.
- return false; // Pretend that this APK does not have a v2 signature.
- } else {
- throw new IOException("Failed to memory-map APK", e);
- }
- }
- // ZipUtils and APK Signature Scheme v2 verifier expect little-endian byte order.
- apkContents.order(ByteOrder.LITTLE_ENDIAN);
-
- final int centralDirOffset =
- (int) getCentralDirOffset(apkContents, getEocdOffset(apkContents));
- // Find the APK Signing Block.
- int apkSigningBlockOffset = findApkSigningBlock(apkContents, centralDirOffset);
- ByteBuffer apkSigningBlock =
- sliceFromTo(apkContents, apkSigningBlockOffset, centralDirOffset);
-
- // Find the APK Signature Scheme v2 Block inside the APK Signing Block.
- findApkSignatureSchemeV2Block(apkSigningBlock);
+ findSignature(apk);
return true;
} catch (SignatureNotFoundException e) {
+ return false;
}
- return false;
}
/**
@@ -135,90 +113,97 @@
* associated with each signer.
*
* @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2.
- * @throws SecurityException if a APK Signature Scheme v2 signature of this APK does not verify.
+ * @throws SecurityException if an APK Signature Scheme v2 signature of this APK does not
+ * verify.
* @throws IOException if an I/O error occurs while reading the APK file.
*/
- public static X509Certificate[][] verify(RandomAccessFile apk)
+ private static X509Certificate[][] verify(RandomAccessFile apk)
throws SignatureNotFoundException, SecurityException, IOException {
-
- long fileSize = apk.length();
- if (fileSize > Integer.MAX_VALUE) {
- throw new IOException("File too large: " + apk.length() + " bytes");
- }
- MappedByteBuffer apkContents;
- try {
- apkContents = apk.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, fileSize);
- // Attempt to preload the contents into memory for faster overall verification (v2 and
- // older) at the expense of somewhat increased latency for rejecting malformed APKs.
- apkContents.load();
- } catch (IOException e) {
- if (e.getCause() instanceof OutOfMemoryError) {
- // TODO: Remove this temporary workaround once verifying large APKs is supported.
- // Very large APKs cannot be memory-mapped. This verification code needs to change
- // to use a different approach for verifying such APKs.
- // This workaround pretends that this APK does not have a v2 signature. This works
- // fine provided the APK is not actually v2-signed. If the APK is v2 signed, v2
- // signature stripping protection inside v1 signature verification code will reject
- // this APK.
- throw new SignatureNotFoundException("Failed to memory-map APK", e);
- } else {
- throw new IOException("Failed to memory-map APK", e);
- }
- }
- return verify(apkContents);
+ SignatureInfo signatureInfo = findSignature(apk);
+ return verify(apk.getFD(), signatureInfo);
}
/**
- * Verifies APK Signature Scheme v2 signatures of the provided APK and returns the certificates
- * associated with each signer.
- *
- * @param apkContents contents of the APK. The contents start at the current position and end
- * at the limit of the buffer.
+ * APK Signature Scheme v2 block and additional information relevant to verifying the signatures
+ * contained in the block against the file.
+ */
+ private static class SignatureInfo {
+ /** Contents of APK Signature Scheme v2 block. */
+ private final ByteBuffer signatureBlock;
+
+ /** Position of the APK Signing Block in the file. */
+ private final long apkSigningBlockOffset;
+
+ /** Position of the ZIP Central Directory in the file. */
+ private final long centralDirOffset;
+
+ /** Position of the ZIP End of Central Directory (EoCD) in the file. */
+ private final long eocdOffset;
+
+ /** Contents of ZIP End of Central Directory (EoCD) of the file. */
+ private final ByteBuffer eocd;
+
+ private SignatureInfo(
+ ByteBuffer signatureBlock,
+ long apkSigningBlockOffset,
+ long centralDirOffset,
+ long eocdOffset,
+ ByteBuffer eocd) {
+ this.signatureBlock = signatureBlock;
+ this.apkSigningBlockOffset = apkSigningBlockOffset;
+ this.centralDirOffset = centralDirOffset;
+ this.eocdOffset = eocdOffset;
+ this.eocd = eocd;
+ }
+ }
+
+ /**
+ * Returns the APK Signature Scheme v2 block contained in the provided APK file and the
+ * additional information relevant for verifying the block against the file.
*
* @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2.
- * @throws SecurityException if a APK Signature Scheme v2 signature of this APK does not verify.
+ * @throws IOException if an I/O error occurs while reading the APK file.
*/
- public static X509Certificate[][] verify(ByteBuffer apkContents)
- throws SignatureNotFoundException, SecurityException {
- // Avoid modifying byte order, position, limit, and mark of the original apkContents.
- apkContents = apkContents.slice();
+ private static SignatureInfo findSignature(RandomAccessFile apk)
+ throws IOException, SignatureNotFoundException {
+ // Find the ZIP End of Central Directory (EoCD) record.
+ Pair<ByteBuffer, Long> eocdAndOffsetInFile = getEocd(apk);
+ ByteBuffer eocd = eocdAndOffsetInFile.first;
+ long eocdOffset = eocdAndOffsetInFile.second;
+ if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(apk, eocdOffset)) {
+ throw new SignatureNotFoundException("ZIP64 APK not supported");
+ }
- // ZipUtils and APK Signature Scheme v2 verifier expect little-endian byte order.
- apkContents.order(ByteOrder.LITTLE_ENDIAN);
-
- final int eocdOffset = getEocdOffset(apkContents);
- final int centralDirOffset = (int) getCentralDirOffset(apkContents, eocdOffset);
-
- // Find the APK Signing Block.
- int apkSigningBlockOffset = findApkSigningBlock(apkContents, centralDirOffset);
- ByteBuffer apkSigningBlock =
- sliceFromTo(apkContents, apkSigningBlockOffset, centralDirOffset);
+ // Find the APK Signing Block. The block immediately precedes the Central Directory.
+ long centralDirOffset = getCentralDirOffset(eocd, eocdOffset);
+ Pair<ByteBuffer, Long> apkSigningBlockAndOffsetInFile =
+ findApkSigningBlock(apk, centralDirOffset);
+ ByteBuffer apkSigningBlock = apkSigningBlockAndOffsetInFile.first;
+ long apkSigningBlockOffset = apkSigningBlockAndOffsetInFile.second;
// Find the APK Signature Scheme v2 Block inside the APK Signing Block.
ByteBuffer apkSignatureSchemeV2Block = findApkSignatureSchemeV2Block(apkSigningBlock);
- // Verify the contents of the APK outside of the APK Signing Block using the APK Signature
- // Scheme v2 Block.
- return verify(
- apkContents,
+ return new SignatureInfo(
apkSignatureSchemeV2Block,
apkSigningBlockOffset,
centralDirOffset,
- eocdOffset);
+ eocdOffset,
+ eocd);
}
/**
- * Verifies the contents outside of the APK Signing Block using the provided APK Signature
- * Scheme v2 Block.
+ * Verifies the contents of the provided APK file against the provided APK Signature Scheme v2
+ * Block.
+ *
+ * @param signatureInfo APK Signature Scheme v2 Block and information relevant for verifying it
+ * against the APK file.
*/
private static X509Certificate[][] verify(
- ByteBuffer apkContents,
- ByteBuffer v2Block,
- int apkSigningBlockOffset,
- int centralDirOffset,
- int eocdOffset) throws SecurityException {
+ FileDescriptor apkFileDescriptor,
+ SignatureInfo signatureInfo) throws SecurityException {
int signerCount = 0;
- Map<Integer, byte[]> contentDigests = new HashMap<>();
+ Map<Integer, byte[]> contentDigests = new ArrayMap<>();
List<X509Certificate[]> signerCerts = new ArrayList<>();
CertificateFactory certFactory;
try {
@@ -228,7 +213,7 @@
}
ByteBuffer signers;
try {
- signers = getLengthPrefixedSlice(v2Block);
+ signers = getLengthPrefixedSlice(signatureInfo.signatureBlock);
} catch (IOException e) {
throw new SecurityException("Failed to read list of signers", e);
}
@@ -255,10 +240,11 @@
verifyIntegrity(
contentDigests,
- apkContents,
- apkSigningBlockOffset,
- centralDirOffset,
- eocdOffset);
+ apkFileDescriptor,
+ signatureInfo.apkSigningBlockOffset,
+ signatureInfo.centralDirOffset,
+ signatureInfo.eocdOffset,
+ signatureInfo.eocd);
return signerCerts.toArray(new X509Certificate[signerCerts.size()][]);
}
@@ -401,25 +387,38 @@
private static void verifyIntegrity(
Map<Integer, byte[]> expectedDigests,
- ByteBuffer apkContents,
- int apkSigningBlockOffset,
- int centralDirOffset,
- int eocdOffset) throws SecurityException {
+ FileDescriptor apkFileDescriptor,
+ long apkSigningBlockOffset,
+ long centralDirOffset,
+ long eocdOffset,
+ ByteBuffer eocdBuf) throws SecurityException {
if (expectedDigests.isEmpty()) {
throw new SecurityException("No digests provided");
}
- ByteBuffer beforeApkSigningBlock = sliceFromTo(apkContents, 0, apkSigningBlockOffset);
- ByteBuffer centralDir = sliceFromTo(apkContents, centralDirOffset, eocdOffset);
+ // We need to verify the integrity of the following three sections of the file:
+ // 1. Everything up to the start of the APK Signing Block.
+ // 2. ZIP Central Directory.
+ // 3. ZIP End of Central Directory (EoCD).
+ // Each of these sections is represented as a separate DataSource instance below.
+
+ // To handle large APKs, these sections are read in 1 MB chunks using memory-mapped I/O to
+ // avoid wasting physical memory. In most APK verification scenarios, the contents of the
+ // APK are already there in the OS's page cache and thus mmap does not use additional
+ // physical memory.
+ DataSource beforeApkSigningBlock =
+ new MemoryMappedFileDataSource(apkFileDescriptor, 0, apkSigningBlockOffset);
+ DataSource centralDir =
+ new MemoryMappedFileDataSource(
+ apkFileDescriptor, centralDirOffset, eocdOffset - centralDirOffset);
+
// For the purposes of integrity verification, ZIP End of Central Directory's field Start of
// Central Directory must be considered to point to the offset of the APK Signing Block.
- byte[] eocdBytes = new byte[apkContents.capacity() - eocdOffset];
- apkContents.position(eocdOffset);
- apkContents.get(eocdBytes);
- ByteBuffer eocd = ByteBuffer.wrap(eocdBytes);
- eocd.order(apkContents.order());
- ZipUtils.setZipEocdCentralDirectoryOffset(eocd, apkSigningBlockOffset);
+ eocdBuf = eocdBuf.duplicate();
+ eocdBuf.order(ByteOrder.LITTLE_ENDIAN);
+ ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, apkSigningBlockOffset);
+ DataSource eocd = new ByteBufferDataSource(eocdBuf);
int[] digestAlgorithms = new int[expectedDigests.size()];
int digestAlgorithmCount = 0;
@@ -427,30 +426,30 @@
digestAlgorithms[digestAlgorithmCount] = digestAlgorithm;
digestAlgorithmCount++;
}
- Map<Integer, byte[]> actualDigests;
+ byte[][] actualDigests;
try {
actualDigests =
computeContentDigests(
digestAlgorithms,
- new ByteBuffer[] {beforeApkSigningBlock, centralDir, eocd});
+ new DataSource[] {beforeApkSigningBlock, centralDir, eocd});
} catch (DigestException e) {
throw new SecurityException("Failed to compute digest(s) of contents", e);
}
- for (Map.Entry<Integer, byte[]> entry : expectedDigests.entrySet()) {
- int digestAlgorithm = entry.getKey();
- byte[] expectedDigest = entry.getValue();
- byte[] actualDigest = actualDigests.get(digestAlgorithm);
+ for (int i = 0; i < digestAlgorithms.length; i++) {
+ int digestAlgorithm = digestAlgorithms[i];
+ byte[] expectedDigest = expectedDigests.get(digestAlgorithm);
+ byte[] actualDigest = actualDigests[i];
if (!MessageDigest.isEqual(expectedDigest, actualDigest)) {
throw new SecurityException(
getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
- + " digest of contents did not verify");
+ + " digest of contents did not verify");
}
}
}
- private static Map<Integer, byte[]> computeContentDigests(
+ private static byte[][] computeContentDigests(
int[] digestAlgorithms,
- ByteBuffer[] contents) throws DigestException {
+ DataSource[] contents) throws DigestException {
// For each digest algorithm the result is computed as follows:
// 1. Each segment of contents is split into consecutive chunks of 1 MB in size.
// The final chunk will be shorter iff the length of segment is not a multiple of 1 MB.
@@ -461,13 +460,18 @@
// chunks (uint32 little-endian) and the concatenation of digests of chunks of all
// segments in-order.
- int totalChunkCount = 0;
- for (ByteBuffer input : contents) {
- totalChunkCount += getChunkCount(input.remaining());
+ long totalChunkCountLong = 0;
+ for (DataSource input : contents) {
+ totalChunkCountLong += getChunkCount(input.size());
}
+ if (totalChunkCountLong >= Integer.MAX_VALUE / 1024) {
+ throw new DigestException("Too many chunks: " + totalChunkCountLong);
+ }
+ int totalChunkCount = (int) totalChunkCountLong;
- Map<Integer, byte[]> digestsOfChunks = new HashMap<>(totalChunkCount);
- for (int digestAlgorithm : digestAlgorithms) {
+ byte[][] digestsOfChunks = new byte[digestAlgorithms.length][];
+ for (int i = 0; i < digestAlgorithms.length; i++) {
+ int digestAlgorithm = digestAlgorithms[i];
int digestOutputSizeBytes = getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
byte[] concatenationOfChunkCountAndChunkDigests =
new byte[5 + totalChunkCount * digestOutputSizeBytes];
@@ -476,49 +480,71 @@
totalChunkCount,
concatenationOfChunkCountAndChunkDigests,
1);
- digestsOfChunks.put(digestAlgorithm, concatenationOfChunkCountAndChunkDigests);
+ digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests;
}
byte[] chunkContentPrefix = new byte[5];
chunkContentPrefix[0] = (byte) 0xa5;
int chunkIndex = 0;
- for (ByteBuffer input : contents) {
- while (input.hasRemaining()) {
- int chunkSize = Math.min(input.remaining(), CHUNK_SIZE_BYTES);
- ByteBuffer chunk = getByteBuffer(input, chunkSize);
- for (int digestAlgorithm : digestAlgorithms) {
- String jcaAlgorithmName =
- getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm);
- MessageDigest md;
- try {
- md = MessageDigest.getInstance(jcaAlgorithmName);
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
- }
- chunk.clear();
- setUnsignedInt32LittleEndian(chunk.remaining(), chunkContentPrefix, 1);
- md.update(chunkContentPrefix);
- md.update(chunk);
- byte[] concatenationOfChunkCountAndChunkDigests =
- digestsOfChunks.get(digestAlgorithm);
+ MessageDigest[] mds = new MessageDigest[digestAlgorithms.length];
+ for (int i = 0; i < digestAlgorithms.length; i++) {
+ String jcaAlgorithmName =
+ getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithms[i]);
+ try {
+ mds[i] = MessageDigest.getInstance(jcaAlgorithmName);
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
+ }
+ }
+ // TODO: Compute digests of chunks in parallel when beneficial. This requires some research
+ // into how to parallelize (if at all) based on the capabilities of the hardware on which
+ // this code is running and based on the size of input.
+ int dataSourceIndex = 0;
+ for (DataSource input : contents) {
+ long inputOffset = 0;
+ long inputRemaining = input.size();
+ while (inputRemaining > 0) {
+ int chunkSize = (int) Math.min(inputRemaining, CHUNK_SIZE_BYTES);
+ setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1);
+ for (int i = 0; i < mds.length; i++) {
+ mds[i].update(chunkContentPrefix);
+ }
+ try {
+ input.feedIntoMessageDigests(mds, inputOffset, chunkSize);
+ } catch (IOException e) {
+ throw new DigestException(
+ "Failed to digest chunk #" + chunkIndex + " of section #"
+ + dataSourceIndex,
+ e);
+ }
+ for (int i = 0; i < digestAlgorithms.length; i++) {
+ int digestAlgorithm = digestAlgorithms[i];
+ byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i];
int expectedDigestSizeBytes =
getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
- int actualDigestSizeBytes = md.digest(concatenationOfChunkCountAndChunkDigests,
- 5 + chunkIndex * expectedDigestSizeBytes, expectedDigestSizeBytes);
+ MessageDigest md = mds[i];
+ int actualDigestSizeBytes =
+ md.digest(
+ concatenationOfChunkCountAndChunkDigests,
+ 5 + chunkIndex * expectedDigestSizeBytes,
+ expectedDigestSizeBytes);
if (actualDigestSizeBytes != expectedDigestSizeBytes) {
throw new RuntimeException(
"Unexpected output size of " + md.getAlgorithm() + " digest: "
+ actualDigestSizeBytes);
}
}
+ inputOffset += chunkSize;
+ inputRemaining -= chunkSize;
chunkIndex++;
}
+ dataSourceIndex++;
}
- Map<Integer, byte[]> result = new HashMap<>(digestAlgorithms.length);
- for (Map.Entry<Integer, byte[]> entry : digestsOfChunks.entrySet()) {
- int digestAlgorithm = entry.getKey();
- byte[] input = entry.getValue();
+ byte[][] result = new byte[digestAlgorithms.length][];
+ for (int i = 0; i < digestAlgorithms.length; i++) {
+ int digestAlgorithm = digestAlgorithms[i];
+ byte[] input = digestsOfChunks[i];
String jcaAlgorithmName = getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm);
MessageDigest md;
try {
@@ -527,49 +553,47 @@
throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
}
byte[] output = md.digest(input);
- result.put(digestAlgorithm, output);
+ result[i] = output;
}
return result;
}
/**
- * Finds the offset of ZIP End of Central Directory (EoCD).
+ * Returns the ZIP End of Central Directory (EoCD) and its offset in the file.
*
- * @throws SignatureNotFoundException If the EoCD could not be found
+ * @throws IOException if an I/O error occurs while reading the file.
+ * @throws SignatureNotFoundException if the EoCD could not be found.
*/
- private static int getEocdOffset(ByteBuffer apkContents) throws SignatureNotFoundException {
- int eocdOffset = ZipUtils.findZipEndOfCentralDirectoryRecord(apkContents);
- if (eocdOffset == -1) {
+ private static Pair<ByteBuffer, Long> getEocd(RandomAccessFile apk)
+ throws IOException, SignatureNotFoundException {
+ Pair<ByteBuffer, Long> eocdAndOffsetInFile =
+ ZipUtils.findZipEndOfCentralDirectoryRecord(apk);
+ if (eocdAndOffsetInFile == null) {
throw new SignatureNotFoundException(
"Not an APK file: ZIP End of Central Directory record not found");
}
- return eocdOffset;
+ return eocdAndOffsetInFile;
}
- private static long getCentralDirOffset(ByteBuffer apkContents, int eocdOffset)
+ private static long getCentralDirOffset(ByteBuffer eocd, long eocdOffset)
throws SignatureNotFoundException {
- if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(apkContents, eocdOffset)) {
- throw new SignatureNotFoundException("ZIP64 APK not supported");
- }
- ByteBuffer eocd = sliceFromTo(apkContents, eocdOffset, apkContents.capacity());
-
// Look up the offset of ZIP Central Directory.
- long centralDirOffsetLong = ZipUtils.getZipEocdCentralDirectoryOffset(eocd);
- if (centralDirOffsetLong >= eocdOffset) {
+ long centralDirOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocd);
+ if (centralDirOffset >= eocdOffset) {
throw new SignatureNotFoundException(
- "ZIP Central Directory offset out of range: " + centralDirOffsetLong
+ "ZIP Central Directory offset out of range: " + centralDirOffset
+ ". ZIP End of Central Directory offset: " + eocdOffset);
}
- long centralDirSizeLong = ZipUtils.getZipEocdCentralDirectorySizeBytes(eocd);
- if (centralDirOffsetLong + centralDirSizeLong != eocdOffset) {
+ long centralDirSize = ZipUtils.getZipEocdCentralDirectorySizeBytes(eocd);
+ if (centralDirOffset + centralDirSize != eocdOffset) {
throw new SignatureNotFoundException(
"ZIP Central Directory is not immediately followed by End of Central"
+ " Directory");
}
- return centralDirOffsetLong;
+ return centralDirOffset;
}
- private static final int getChunkCount(int inputSizeBytes) {
+ private static final long getChunkCount(long inputSizeBytes) {
return (inputSizeBytes + CHUNK_SIZE_BYTES - 1) / CHUNK_SIZE_BYTES;
}
@@ -837,10 +861,9 @@
private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
- private static int findApkSigningBlock(ByteBuffer apkContents, int centralDirOffset)
- throws SignatureNotFoundException {
- checkByteOrderLittleEndian(apkContents);
-
+ private static Pair<ByteBuffer, Long> findApkSigningBlock(
+ RandomAccessFile apk, long centralDirOffset)
+ throws IOException, SignatureNotFoundException {
// FORMAT:
// OFFSET DATA TYPE DESCRIPTION
// * @+0 bytes uint64: size in bytes (excluding this field)
@@ -853,32 +876,42 @@
"APK too small for APK Signing Block. ZIP Central Directory offset: "
+ centralDirOffset);
}
- // Check magic field present
- if ((apkContents.getLong(centralDirOffset - 16) != APK_SIG_BLOCK_MAGIC_LO)
- || (apkContents.getLong(centralDirOffset - 8) != APK_SIG_BLOCK_MAGIC_HI)) {
+ // Read the magic and offset in file from the footer section of the block:
+ // * uint64: size of block
+ // * 16 bytes: magic
+ ByteBuffer footer = ByteBuffer.allocate(24);
+ footer.order(ByteOrder.LITTLE_ENDIAN);
+ apk.seek(centralDirOffset - footer.capacity());
+ apk.readFully(footer.array(), footer.arrayOffset(), footer.capacity());
+ if ((footer.getLong(8) != APK_SIG_BLOCK_MAGIC_LO)
+ || (footer.getLong(16) != APK_SIG_BLOCK_MAGIC_HI)) {
throw new SignatureNotFoundException(
"No APK Signing Block before ZIP Central Directory");
}
// Read and compare size fields
- long apkSigBlockSizeLong = apkContents.getLong(centralDirOffset - 24);
- if ((apkSigBlockSizeLong < 24) || (apkSigBlockSizeLong > Integer.MAX_VALUE - 8)) {
+ long apkSigBlockSizeInFooter = footer.getLong(0);
+ if ((apkSigBlockSizeInFooter < footer.capacity())
+ || (apkSigBlockSizeInFooter > Integer.MAX_VALUE - 8)) {
throw new SignatureNotFoundException(
- "APK Signing Block size out of range: " + apkSigBlockSizeLong);
+ "APK Signing Block size out of range: " + apkSigBlockSizeInFooter);
}
- int apkSigBlockSizeFromFooter = (int) apkSigBlockSizeLong;
- int totalSize = apkSigBlockSizeFromFooter + 8;
- int apkSigBlockOffset = centralDirOffset - totalSize;
+ int totalSize = (int) (apkSigBlockSizeInFooter + 8);
+ long apkSigBlockOffset = centralDirOffset - totalSize;
if (apkSigBlockOffset < 0) {
throw new SignatureNotFoundException(
"APK Signing Block offset out of range: " + apkSigBlockOffset);
}
- long apkSigBlockSizeFromHeader = apkContents.getLong(apkSigBlockOffset);
- if (apkSigBlockSizeFromHeader != apkSigBlockSizeFromFooter) {
+ ByteBuffer apkSigBlock = ByteBuffer.allocate(totalSize);
+ apkSigBlock.order(ByteOrder.LITTLE_ENDIAN);
+ apk.seek(apkSigBlockOffset);
+ apk.readFully(apkSigBlock.array(), apkSigBlock.arrayOffset(), apkSigBlock.capacity());
+ long apkSigBlockSizeInHeader = apkSigBlock.getLong(0);
+ if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) {
throw new SignatureNotFoundException(
"APK Signing Block sizes in header and footer do not match: "
- + apkSigBlockSizeFromHeader + " vs " + apkSigBlockSizeFromFooter);
+ + apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter);
}
- return apkSigBlockOffset;
+ return Pair.create(apkSigBlock, apkSigBlockOffset);
}
private static ByteBuffer findApkSignatureSchemeV2Block(ByteBuffer apkSigningBlock)
@@ -930,6 +963,8 @@
}
public static class SignatureNotFoundException extends Exception {
+ private static final long serialVersionUID = 1L;
+
public SignatureNotFoundException(String message) {
super(message);
}
@@ -940,6 +975,159 @@
}
/**
+ * Source of data to be digested.
+ */
+ private static interface DataSource {
+
+ /**
+ * Returns the size (in bytes) of the data offered by this source.
+ */
+ long size();
+
+ /**
+ * Feeds the specified region of this source's data into the provided digests. Each digest
+ * instance gets the same data.
+ *
+ * @param offset offset of the region inside this data source.
+ * @param size size (in bytes) of the region.
+ */
+ void feedIntoMessageDigests(MessageDigest[] mds, long offset, int size) throws IOException;
+ }
+
+ /**
+ * {@link DataSource} which provides data from a file descriptor by memory-mapping the sections
+ * of the file requested by
+ * {@link DataSource#feedIntoMessageDigests(MessageDigest[], long, int) feedIntoMessageDigests}.
+ */
+ private static final class MemoryMappedFileDataSource implements DataSource {
+ private static final Os OS = Libcore.os;
+ private static final long MEMORY_PAGE_SIZE_BYTES = OS.sysconf(OsConstants._SC_PAGESIZE);
+
+ private final FileDescriptor mFd;
+ private final long mFilePosition;
+ private final long mSize;
+
+ /**
+ * Constructs a new {@code MemoryMappedFileDataSource} for the specified region of the file.
+ *
+ * @param position start position of the region in the file.
+ * @param size size (in bytes) of the region.
+ */
+ public MemoryMappedFileDataSource(FileDescriptor fd, long position, long size) {
+ mFd = fd;
+ mFilePosition = position;
+ mSize = size;
+ }
+
+ @Override
+ public long size() {
+ return mSize;
+ }
+
+ @Override
+ public void feedIntoMessageDigests(
+ MessageDigest[] mds, long offset, int size) throws IOException {
+ // IMPLEMENTATION NOTE: After a lot of experimentation, the implementation of this
+ // method was settled on a straightforward mmap with prefaulting.
+ //
+ // This method is not using FileChannel.map API because that API does not offset a way
+ // to "prefault" the resulting memory pages. Without prefaulting, performance is about
+ // 10% slower on small to medium APKs, but is significantly worse for APKs in 500+ MB
+ // range. FileChannel.load (which currently uses madvise) doesn't help. Finally,
+ // invoking madvise (MADV_SEQUENTIAL) after mmap with prefaulting wastes quite a bit of
+ // time, which is not compensated for by faster reads.
+
+ // We mmap the smallest region of the file containing the requested data. mmap requires
+ // that the start offset in the file must be a multiple of memory page size. We thus may
+ // need to mmap from an offset less than the requested offset.
+ long filePosition = mFilePosition + offset;
+ long mmapFilePosition =
+ (filePosition / MEMORY_PAGE_SIZE_BYTES) * MEMORY_PAGE_SIZE_BYTES;
+ int dataStartOffsetInMmapRegion = (int) (filePosition - mmapFilePosition);
+ long mmapRegionSize = size + dataStartOffsetInMmapRegion;
+ long mmapPtr = 0;
+ try {
+ mmapPtr = OS.mmap(
+ 0, // let the OS choose the start address of the region in memory
+ mmapRegionSize,
+ OsConstants.PROT_READ,
+ OsConstants.MAP_SHARED | OsConstants.MAP_POPULATE, // "prefault" all pages
+ mFd,
+ mmapFilePosition);
+ // Feeding a memory region into MessageDigest requires the region to be represented
+ // as a direct ByteBuffer.
+ ByteBuffer buf = new DirectByteBuffer(
+ size,
+ mmapPtr + dataStartOffsetInMmapRegion,
+ mFd, // not really needed, but just in case
+ null, // no need to clean up -- it's taken care of by the finally block
+ true // read only buffer
+ );
+ for (MessageDigest md : mds) {
+ buf.position(0);
+ md.update(buf);
+ }
+ } catch (ErrnoException e) {
+ throw new IOException("Failed to mmap " + mmapRegionSize + " bytes", e);
+ } finally {
+ if (mmapPtr != 0) {
+ try {
+ OS.munmap(mmapPtr, mmapRegionSize);
+ } catch (ErrnoException ignored) {}
+ }
+ }
+ }
+ }
+
+ /**
+ * {@link DataSource} which provides data from a {@link ByteBuffer}.
+ */
+ private static final class ByteBufferDataSource implements DataSource {
+ /**
+ * Underlying buffer. The data is stored between position 0 and the buffer's capacity.
+ * The buffer's position is 0 and limit is equal to capacity.
+ */
+ private final ByteBuffer mBuf;
+
+ public ByteBufferDataSource(ByteBuffer buf) {
+ // Defensive copy, to avoid changes to mBuf being visible in buf.
+ mBuf = buf.slice();
+ }
+
+ @Override
+ public long size() {
+ return mBuf.capacity();
+ }
+
+ @Override
+ public void feedIntoMessageDigests(
+ MessageDigest[] mds, long offset, int size) throws IOException {
+ // There's no way to tell MessageDigest to read data from ByteBuffer from a position
+ // other than the buffer's current position. We thus need to change the buffer's
+ // position to match the requested offset.
+ //
+ // In the future, it may be necessary to compute digests of multiple regions in
+ // parallel. Given that digest computation is a slow operation, we enable multiple
+ // such requests to be fulfilled by this instance. This is achieved by serially
+ // creating a new ByteBuffer corresponding to the requested data range and then,
+ // potentially concurrently, feeding these buffers into MessageDigest instances.
+ ByteBuffer region;
+ synchronized (mBuf) {
+ mBuf.position((int) offset);
+ mBuf.limit((int) offset + size);
+ region = mBuf.slice();
+ }
+
+ for (MessageDigest md : mds) {
+ // Need to reset position to 0 at the start of each iteration because
+ // MessageDigest.update below sets it to the buffer's limit.
+ region.position(0);
+ md.update(region);
+ }
+ }
+ }
+
+ /**
* For legacy reasons we need to return exactly the original encoded certificate bytes, instead
* of letting the underlying implementation have a shot at re-encoding the data.
*/
diff --git a/core/java/android/util/apk/ZipUtils.java b/core/java/android/util/apk/ZipUtils.java
index a383d5c..cdbac18 100644
--- a/core/java/android/util/apk/ZipUtils.java
+++ b/core/java/android/util/apk/ZipUtils.java
@@ -16,13 +16,17 @@
package android.util.apk;
+import android.util.Pair;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* Assorted ZIP format helpers.
*
- * <p>NOTE: Most helper methods operating on {@code ByteBuffer} instances except that the byte
+ * <p>NOTE: Most helper methods operating on {@code ByteBuffer} instances expect that the byte
* order of these buffers is little-endian.
*/
abstract class ZipUtils {
@@ -35,9 +39,101 @@
private static final int ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET = 20;
private static final int ZIP64_EOCD_LOCATOR_SIZE = 20;
- private static final int ZIP64_EOCD_LOCATOR_SIG = 0x07064b50;
+ private static final int ZIP64_EOCD_LOCATOR_SIG_REVERSE_BYTE_ORDER = 0x504b0607;
- private static final int UINT32_MAX_VALUE = 0xffff;
+ private static final int UINT16_MAX_VALUE = 0xffff;
+
+ /**
+ * Returns the ZIP End of Central Directory record of the provided ZIP file.
+ *
+ * @return contents of the ZIP End of Central Directory record and the record's offset in the
+ * file or {@code null} if the file does not contain the record.
+ *
+ * @throws IOException if an I/O error occurs while reading the file.
+ */
+ static Pair<ByteBuffer, Long> findZipEndOfCentralDirectoryRecord(RandomAccessFile zip)
+ throws IOException {
+ // ZIP End of Central Directory (EOCD) record is located at the very end of the ZIP archive.
+ // The record can be identified by its 4-byte signature/magic which is located at the very
+ // beginning of the record. A complication is that the record is variable-length because of
+ // the comment field.
+ // The algorithm for locating the ZIP EOCD record is as follows. We search backwards from
+ // end of the buffer for the EOCD record signature. Whenever we find a signature, we check
+ // the candidate record's comment length is such that the remainder of the record takes up
+ // exactly the remaining bytes in the buffer. The search is bounded because the maximum
+ // size of the comment field is 65535 bytes because the field is an unsigned 16-bit number.
+
+ long fileSize = zip.length();
+ if (fileSize < ZIP_EOCD_REC_MIN_SIZE) {
+ return null;
+ }
+
+ // Optimization: 99.99% of APKs have a zero-length comment field in the EoCD record and thus
+ // the EoCD record offset is known in advance. Try that offset first to avoid unnecessarily
+ // reading more data.
+ Pair<ByteBuffer, Long> result = findZipEndOfCentralDirectoryRecord(zip, 0);
+ if (result != null) {
+ return result;
+ }
+
+ // EoCD does not start where we expected it to. Perhaps it contains a non-empty comment
+ // field. Expand the search. The maximum size of the comment field in EoCD is 65535 because
+ // the comment length field is an unsigned 16-bit number.
+ return findZipEndOfCentralDirectoryRecord(zip, UINT16_MAX_VALUE);
+ }
+
+ /**
+ * Returns the ZIP End of Central Directory record of the provided ZIP file.
+ *
+ * @param maxCommentSize maximum accepted size (in bytes) of EoCD comment field. The permitted
+ * value is from 0 to 65535 inclusive. The smaller the value, the faster this method
+ * locates the record, provided its comment field is no longer than this value.
+ *
+ * @return contents of the ZIP End of Central Directory record and the record's offset in the
+ * file or {@code null} if the file does not contain the record.
+ *
+ * @throws IOException if an I/O error occurs while reading the file.
+ */
+ private static Pair<ByteBuffer, Long> findZipEndOfCentralDirectoryRecord(
+ RandomAccessFile zip, int maxCommentSize) throws IOException {
+ // ZIP End of Central Directory (EOCD) record is located at the very end of the ZIP archive.
+ // The record can be identified by its 4-byte signature/magic which is located at the very
+ // beginning of the record. A complication is that the record is variable-length because of
+ // the comment field.
+ // The algorithm for locating the ZIP EOCD record is as follows. We search backwards from
+ // end of the buffer for the EOCD record signature. Whenever we find a signature, we check
+ // the candidate record's comment length is such that the remainder of the record takes up
+ // exactly the remaining bytes in the buffer. The search is bounded because the maximum
+ // size of the comment field is 65535 bytes because the field is an unsigned 16-bit number.
+
+ if ((maxCommentSize < 0) || (maxCommentSize > UINT16_MAX_VALUE)) {
+ throw new IllegalArgumentException("maxCommentSize: " + maxCommentSize);
+ }
+
+ long fileSize = zip.length();
+ if (fileSize < ZIP_EOCD_REC_MIN_SIZE) {
+ // No space for EoCD record in the file.
+ return null;
+ }
+ // Lower maxCommentSize if the file is too small.
+ maxCommentSize = (int) Math.min(maxCommentSize, fileSize - ZIP_EOCD_REC_MIN_SIZE);
+
+ ByteBuffer buf = ByteBuffer.allocate(ZIP_EOCD_REC_MIN_SIZE + maxCommentSize);
+ buf.order(ByteOrder.LITTLE_ENDIAN);
+ long bufOffsetInFile = fileSize - buf.capacity();
+ zip.seek(bufOffsetInFile);
+ zip.readFully(buf.array(), buf.arrayOffset(), buf.capacity());
+ int eocdOffsetInBuf = findZipEndOfCentralDirectoryRecord(buf);
+ if (eocdOffsetInBuf == -1) {
+ // No EoCD record found in the buffer
+ return null;
+ }
+ // EoCD found
+ buf.position(eocdOffsetInBuf);
+ ByteBuffer eocd = buf.slice();
+ eocd.order(ByteOrder.LITTLE_ENDIAN);
+ return Pair.create(eocd, bufOffsetInFile + eocdOffsetInBuf);
+ }
/**
* Returns the position at which ZIP End of Central Directory record starts in the provided
@@ -45,7 +141,7 @@
*
* <p>NOTE: Byte order of {@code zipContents} must be little-endian.
*/
- public static int findZipEndOfCentralDirectoryRecord(ByteBuffer zipContents) {
+ private static int findZipEndOfCentralDirectoryRecord(ByteBuffer zipContents) {
assertByteOrderLittleEndian(zipContents);
// ZIP End of Central Directory (EOCD) record is located at the very end of the ZIP archive.
@@ -56,14 +152,13 @@
// end of the buffer for the EOCD record signature. Whenever we find a signature, we check
// the candidate record's comment length is such that the remainder of the record takes up
// exactly the remaining bytes in the buffer. The search is bounded because the maximum
- // size of the comment field is 65535 bytes because the field is an unsigned 32-bit number.
+ // size of the comment field is 65535 bytes because the field is an unsigned 16-bit number.
int archiveSize = zipContents.capacity();
if (archiveSize < ZIP_EOCD_REC_MIN_SIZE) {
- System.out.println("File size smaller than EOCD min size");
return -1;
}
- int maxCommentLength = Math.min(archiveSize - ZIP_EOCD_REC_MIN_SIZE, UINT32_MAX_VALUE);
+ int maxCommentLength = Math.min(archiveSize - ZIP_EOCD_REC_MIN_SIZE, UINT16_MAX_VALUE);
int eocdWithEmptyCommentStartPosition = archiveSize - ZIP_EOCD_REC_MIN_SIZE;
for (int expectedCommentLength = 0; expectedCommentLength < maxCommentLength;
expectedCommentLength++) {
@@ -82,24 +177,28 @@
}
/**
- * Returns {@code true} if the provided buffer contains a ZIP64 End of Central Directory
+ * Returns {@code true} if the provided file contains a ZIP64 End of Central Directory
* Locator.
*
- * <p>NOTE: Byte order of {@code zipContents} must be little-endian.
+ * @param zipEndOfCentralDirectoryPosition offset of the ZIP End of Central Directory record
+ * in the file.
+ *
+ * @throws IOException if an I/O error occurs while reading the file.
*/
public static final boolean isZip64EndOfCentralDirectoryLocatorPresent(
- ByteBuffer zipContents, int zipEndOfCentralDirectoryPosition) {
- assertByteOrderLittleEndian(zipContents);
+ RandomAccessFile zip, long zipEndOfCentralDirectoryPosition) throws IOException {
// ZIP64 End of Central Directory Locator immediately precedes the ZIP End of Central
// Directory Record.
-
- int locatorPosition = zipEndOfCentralDirectoryPosition - ZIP64_EOCD_LOCATOR_SIZE;
+ long locatorPosition = zipEndOfCentralDirectoryPosition - ZIP64_EOCD_LOCATOR_SIZE;
if (locatorPosition < 0) {
return false;
}
- return zipContents.getInt(locatorPosition) == ZIP64_EOCD_LOCATOR_SIG;
+ zip.seek(locatorPosition);
+ // RandomAccessFile.readInt assumes big-endian byte order, but ZIP format uses
+ // little-endian.
+ return zip.readInt() == ZIP64_EOCD_LOCATOR_SIG_REVERSE_BYTE_ORDER;
}
/**
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 6a2cc80..a1e2e94 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -112,15 +112,18 @@
*
* @param window The window being modified. Must be attached to a parent window
* or this call will fail.
- * @param x The new x position
- * @param y The new y position
- * @param width The new width
- * @param height The new height
+ * @param left The new left position
+ * @param top The new top position
+ * @param right The new right position
+ * @param bottom The new bottom position
+ * @param requestedWidth The new requested width
+ * @param requestedHeight The new requested height
* @param deferTransactionUntilFrame Frame number from our parent (attached) to
* defer this action until.
* @param outFrame Rect in which is placed the new position/size on screen.
*/
void repositionChild(IWindow childWindow, int left, int top, int right, int bottom,
+ int requestedWidth, int requestedHeight,
long deferTransactionUntilFrame, out Rect outFrame);
/*
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 2c9d691..477ffd9 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -665,7 +665,9 @@
"postion = [%d, %d, %d, %d]", mWindowSpaceLeft, mWindowSpaceTop,
mLocation[0], mLocation[1]));
mSession.repositionChild(mWindow, mWindowSpaceLeft, mWindowSpaceTop,
- mLocation[0], mLocation[1], -1, mWinFrame);
+ mLocation[0], mLocation[1],
+ mWindowSpaceWidth, mWindowSpaceHeight,
+ -1, mWinFrame);
} catch (RemoteException ex) {
Log.e(TAG, "Exception from relayout", ex);
}
@@ -700,7 +702,9 @@
right, bottom));
}
// Just using mRTLastReportedPosition as a dummy rect here
- session.repositionChild(window, left, top, right, bottom, frameNumber,
+ session.repositionChild(window, left, top, right, bottom,
+ mWindowSpaceWidth, mWindowSpaceHeight,
+ frameNumber,
mRTLastReportedPosition);
// Now overwrite mRTLastReportedPosition with our values
mRTLastReportedPosition.set(left, top, right, bottom);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index e5ab971..6d35a58 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5020,8 +5020,8 @@
if (scrollabilityCache.scrollBar == null) {
scrollabilityCache.scrollBar = new ScrollBarDrawable();
- scrollabilityCache.scrollBar.setCallback(this);
scrollabilityCache.scrollBar.setState(getDrawableState());
+ scrollabilityCache.scrollBar.setCallback(this);
}
final boolean fadeScrollbars = a.getBoolean(R.styleable.View_fadeScrollbars, true);
@@ -13295,8 +13295,8 @@
if (scrollCache.scrollBar == null) {
scrollCache.scrollBar = new ScrollBarDrawable();
- scrollCache.scrollBar.setCallback(this);
scrollCache.scrollBar.setState(getDrawableState());
+ scrollCache.scrollBar.setCallback(this);
}
if (isHorizontalScrollBarEnabled() || isVerticalScrollBarEnabled()) {
@@ -18002,7 +18002,13 @@
* to clear the previous drawable. setVisible first while we still have the callback set.
*/
if (mBackground != null) {
- if (isAttachedToWindow()) {
+ // It's possible for this method to be invoked from the View constructor before
+ // subclass constructors have run. Drawables can and should trigger invalidations
+ // and other activity with their callback on visibility changes, which shouldn't
+ // happen before subclass constructors finish. However, we won't have set the
+ // drawable as visible until the view becomes attached. This guard below keeps
+ // multiple calls to this method from constructors from causing issues.
+ if (mBackground.isVisible()) {
mBackground.setVisible(false, false);
}
mBackground.setCallback(null);
@@ -18049,7 +18055,6 @@
// which requires the view set as the callback (us) to recognize the drawable as
// belonging to it as per verifyDrawable.
mBackground = background;
- background.setCallback(this);
if (background.isStateful()) {
background.setState(getDrawableState());
}
@@ -18059,6 +18064,9 @@
applyBackgroundTint();
+ // Set callback last, since the view may still be initializing.
+ background.setCallback(this);
+
if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;
requestLayout = true;
@@ -18235,7 +18243,13 @@
}
if (mForegroundInfo.mDrawable != null) {
- if (isAttachedToWindow()) {
+ // It's possible for this method to be invoked from the View constructor before
+ // subclass constructors have run. Drawables can and should trigger invalidations
+ // and other activity with their callback on visibility changes, which shouldn't
+ // happen before subclass constructors finish. However, we won't have set the
+ // drawable as visible until the view becomes attached. This guard below keeps
+ // multiple calls to this method from constructors from causing issues.
+ if (mForegroundInfo.mDrawable.isVisible()) {
mForegroundInfo.mDrawable.setVisible(false, false);
}
mForegroundInfo.mDrawable.setCallback(null);
@@ -18248,7 +18262,6 @@
if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;
}
- foreground.setCallback(this);
foreground.setLayoutDirection(getLayoutDirection());
if (foreground.isStateful()) {
foreground.setState(getDrawableState());
@@ -18257,6 +18270,8 @@
if (isAttachedToWindow()) {
foreground.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);
}
+ // Set callback last, since the view may still be initializing.
+ foreground.setCallback(this);
} else if ((mViewFlags & WILL_NOT_DRAW) != 0 && mBackground == null) {
mPrivateFlags |= PFLAG_SKIP_DRAW;
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 97cafb7..cf5cc3e 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -975,7 +975,17 @@
* shown to the user, if needed. Call this if the user interacts with
* your view in such a way that they have expressed they would like to
* start performing input into it.
- *
+ *
+ * <p><strong>Caveat:</strong> {@link ResultReceiver} instance passed to
+ * this method can be a long-lived object, because it may not be
+ * garbage-collected until all the corresponding {@link ResultReceiver}
+ * objects transferred to different processes get garbage-collected.
+ * Follow the general patterns to avoid memory leaks in Android.
+ * Consider to use {@link java.lang.ref.WeakReference} so that application
+ * logic objects such as {@link android.app.Activity} and {@link Context}
+ * can be garbage collected regardless of the lifetime of
+ * {@link ResultReceiver}.
+ *
* @param view The currently focused view, which would like to receive
* soft keyboard input.
* @param flags Provides additional operating flags. Currently may be
@@ -1044,7 +1054,17 @@
* that is currently accepting input. This should be called as a result
* of the user doing some actually than fairly explicitly requests to
* have the input window hidden.
- *
+ *
+ * <p><strong>Caveat:</strong> {@link ResultReceiver} instance passed to
+ * this method can be a long-lived object, because it may not be
+ * garbage-collected until all the corresponding {@link ResultReceiver}
+ * objects transferred to different processes get garbage-collected.
+ * Follow the general patterns to avoid memory leaks in Android.
+ * Consider to use {@link java.lang.ref.WeakReference} so that application
+ * logic objects such as {@link android.app.Activity} and {@link Context}
+ * can be garbage collected regardless of the lifetime of
+ * {@link ResultReceiver}.
+ *
* @param windowToken The token of the window that is making the request,
* as returned by {@link View#getWindowToken() View.getWindowToken()}.
* @param flags Provides additional operating flags. Currently may be
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index 222a040..0206577 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -911,11 +911,17 @@
}
if (mDrawable != null) {
- mDrawable.setCallback(null);
- unscheduleDrawable(mDrawable);
- if (isAttachedToWindow()) {
+ // It's possible for this method to be invoked from the constructor before
+ // subclass constructors have run. Drawables can and should trigger invalidations
+ // and other activity with their callback on visibility changes, which shouldn't
+ // happen before subclass constructors finish. However, we won't have set the
+ // drawable as visible until the view becomes attached. This guard below keeps
+ // multiple calls to this method from constructors from causing issues.
+ if (mDrawable.isVisible()) {
mDrawable.setVisible(false, false);
}
+ mDrawable.setCallback(null);
+ unscheduleDrawable(mDrawable);
}
mDrawable = d;
diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java
index 36e0c77..6a10743 100644
--- a/core/java/android/widget/ListPopupWindow.java
+++ b/core/java/android/widget/ListPopupWindow.java
@@ -1183,28 +1183,28 @@
}
// getMaxAvailableHeight() subtracts the padding, so we put it back
- // to get the available height for the whole window
- int padding = 0;
- Drawable background = mPopup.getBackground();
+ // to get the available height for the whole window.
+ final int padding;
+ final Drawable background = mPopup.getBackground();
if (background != null) {
background.getPadding(mTempRect);
padding = mTempRect.top + mTempRect.bottom;
- // If we don't have an explicit vertical offset, determine one from the window
- // background so that content will line up.
+ // If we don't have an explicit vertical offset, determine one from
+ // the window background so that content will line up.
if (!mDropDownVerticalOffsetSet) {
mDropDownVerticalOffset = -mTempRect.top;
}
} else {
mTempRect.setEmpty();
+ padding = 0;
}
// Max height available on the screen for a popup.
- boolean ignoreBottomDecorations =
+ final boolean ignoreBottomDecorations =
mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED;
final int maxHeight = mPopup.getMaxAvailableHeight(
getAnchorView(), mDropDownVerticalOffset, ignoreBottomDecorations);
-
if (mDropDownAlwaysVisible || mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
return maxHeight + padding;
}
@@ -1213,25 +1213,30 @@
switch (mDropDownWidth) {
case ViewGroup.LayoutParams.WRAP_CONTENT:
childWidthSpec = MeasureSpec.makeMeasureSpec(
- mContext.getResources().getDisplayMetrics().widthPixels -
- (mTempRect.left + mTempRect.right),
+ mContext.getResources().getDisplayMetrics().widthPixels
+ - (mTempRect.left + mTempRect.right),
MeasureSpec.AT_MOST);
break;
case ViewGroup.LayoutParams.MATCH_PARENT:
childWidthSpec = MeasureSpec.makeMeasureSpec(
- mContext.getResources().getDisplayMetrics().widthPixels -
- (mTempRect.left + mTempRect.right),
+ mContext.getResources().getDisplayMetrics().widthPixels
+ - (mTempRect.left + mTempRect.right),
MeasureSpec.EXACTLY);
break;
default:
childWidthSpec = MeasureSpec.makeMeasureSpec(mDropDownWidth, MeasureSpec.EXACTLY);
break;
}
+
+ // Add padding only if the list has items in it, that way we don't show
+ // the popup if it is not needed.
final int listContent = mDropDownList.measureHeightOfChildren(childWidthSpec,
- 0, ListView.NO_POSITION, maxHeight - otherHeights, -1);
- // add padding only if the list has items in it, that way we don't show
- // the popup if it is not needed
- if (listContent > 0) otherHeights += padding;
+ 0, DropDownListView.NO_POSITION, maxHeight - otherHeights, -1);
+ if (listContent > 0) {
+ final int listPadding = mDropDownList.getPaddingTop()
+ + mDropDownList.getPaddingBottom();
+ otherHeights += padding + listPadding;
+ }
return listContent + otherHeights;
}
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index a1417f0..d1b5fc8 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -2027,21 +2027,24 @@
mAnchorYoff = yoff;
}
+ final WindowManager.LayoutParams p =
+ (WindowManager.LayoutParams) mDecorView.getLayoutParams();
+
if (updateDimension) {
if (width == -1) {
width = mPopupWidth;
} else {
mPopupWidth = width;
+ p.width = width;
}
if (height == -1) {
height = mPopupHeight;
} else {
mPopupHeight = height;
+ p.height = height;
}
}
- final WindowManager.LayoutParams p =
- (WindowManager.LayoutParams) mDecorView.getLayoutParams();
final int x = p.x;
final int y = p.y;
if (updateLocation) {
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 063288e..8bd63df 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -60,6 +60,7 @@
import libcore.util.Objects;
import com.android.internal.R;
+import com.android.internal.util.Preconditions;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -1096,6 +1097,13 @@
memoryCounter.addBitmapMemory(mBitmaps.get(i));
}
}
+
+ @Override
+ protected BitmapCache clone() {
+ BitmapCache bitmapCache = new BitmapCache();
+ bitmapCache.mBitmaps.addAll(mBitmaps);
+ return bitmapCache;
+ }
}
private class BitmapReflectionAction extends Action {
@@ -2227,10 +2235,21 @@
public RemoteViews clone() {
+ Preconditions.checkState(mIsRoot, "RemoteView has been attached to another RemoteView. "
+ + "May only clone the root of a RemoteView hierarchy.");
+
Parcel p = Parcel.obtain();
+
+ // Do not parcel the Bitmap cache - doing so creates an expensive copy of all bitmaps.
+ // Instead pretend we're not owning the cache while parceling.
+ mIsRoot = false;
writeToParcel(p, 0);
p.setDataPosition(0);
- RemoteViews rv = new RemoteViews(p);
+ mIsRoot = true;
+
+ RemoteViews rv = new RemoteViews(p, mBitmapCache.clone());
+ rv.mIsRoot = true;
+
p.recycle();
return rv;
}
@@ -2240,7 +2259,7 @@
}
/**
- * Reutrns the layout id of the root layout associated with this RemoteViews. In the case
+ * Returns the layout id of the root layout associated with this RemoteViews. In the case
* that the RemoteViews has both a landscape and portrait root, this will return the layout
* id associated with the portrait layout.
*
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 80e1db0..5726de1 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -76,6 +76,7 @@
import java.util.Objects;
import java.util.Set;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
@@ -507,7 +508,9 @@
mPackageMonitor.unregister();
mRegistered = false;
}
- if ((getIntent().getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 && !isVoiceInteraction()) {
+ final Intent intent = getIntent();
+ if ((intent.getFlags() & FLAG_ACTIVITY_NEW_TASK) != 0 && !isVoiceInteraction()
+ && !mResolvingHome) {
// This resolver is in the unusual situation where it has been
// launched at the top of a new task. We don't let it be added
// to the recent tasks shown to the user, and we need to make sure
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index c6db0ed..c685b0c 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -117,13 +117,10 @@
android/graphics/Interpolator.cpp \
android/graphics/MaskFilter.cpp \
android/graphics/Matrix.cpp \
- android/graphics/MinikinSkia.cpp \
- android/graphics/MinikinUtils.cpp \
android/graphics/Movie.cpp \
android/graphics/NinePatch.cpp \
android/graphics/NinePatchPeeker.cpp \
android/graphics/Paint.cpp \
- android/graphics/PaintImpl.cpp \
android/graphics/Path.cpp \
android/graphics/PathMeasure.cpp \
android/graphics/PathEffect.cpp \
@@ -135,7 +132,6 @@
android/graphics/Shader.cpp \
android/graphics/SurfaceTexture.cpp \
android/graphics/Typeface.cpp \
- android/graphics/TypefaceImpl.cpp \
android/graphics/Utils.cpp \
android/graphics/Xfermode.cpp \
android/graphics/YuvToJpegEncoder.cpp \
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 43e26b3..27b9830 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -1,7 +1,6 @@
#define LOG_TAG "Bitmap"
#include "Bitmap.h"
-#include "Paint.h"
#include "SkBitmap.h"
#include "SkPixelRef.h"
#include "SkImageEncoder.h"
@@ -18,6 +17,7 @@
#include "android_nio_utils.h"
#include "CreateJavaOutputStreamAdaptor.h"
#include <Caches.h>
+#include <hwui/Paint.h>
#include "core_jni_helpers.h"
diff --git a/core/jni/android/graphics/Camera.cpp b/core/jni/android/graphics/Camera.cpp
index 6fcf689..76d6851 100644
--- a/core/jni/android/graphics/Camera.cpp
+++ b/core/jni/android/graphics/Camera.cpp
@@ -3,8 +3,8 @@
#include "SkCamera.h"
-#include "Canvas.h"
#include "GraphicsJNI.h"
+#include <hwui/Canvas.h>
static jfieldID gNativeInstanceFieldID;
diff --git a/core/jni/android/graphics/CanvasProperty.cpp b/core/jni/android/graphics/CanvasProperty.cpp
index 728bc1c..c841d6a 100644
--- a/core/jni/android/graphics/CanvasProperty.cpp
+++ b/core/jni/android/graphics/CanvasProperty.cpp
@@ -16,9 +16,9 @@
#include "jni.h"
#include "GraphicsJNI.h"
-#include "Paint.h"
#include <core_jni_helpers.h>
+#include <hwui/Paint.h>
#include <utils/RefBase.h>
#include <CanvasProperty.h>
diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp
index 2e974a3..6dc251c 100644
--- a/core/jni/android/graphics/FontFamily.cpp
+++ b/core/jni/android/graphics/FontFamily.cpp
@@ -31,9 +31,9 @@
#include <androidfw/AssetManager.h>
#include "Utils.h"
-#include "TypefaceImpl.h"
+#include <hwui/MinikinSkia.h>
+#include <hwui/Typeface.h>
#include <minikin/FontFamily.h>
-#include "MinikinSkia.h"
#include <memory>
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index bd01c73..1ead618 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -7,13 +7,13 @@
#include "JNIHelp.h"
#include "GraphicsJNI.h"
-#include "Canvas.h"
#include "SkCanvas.h"
#include "SkDevice.h"
#include "SkMath.h"
#include "SkRegion.h"
#include <android_runtime/AndroidRuntime.h>
#include <cutils/ashmem.h>
+#include <hwui/Canvas.h>
#include <Caches.h>
#include <TextureCache.h>
diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h
index e99a3ff..5baa8f8 100644
--- a/core/jni/android/graphics/GraphicsJNI.h
+++ b/core/jni/android/graphics/GraphicsJNI.h
@@ -11,15 +11,15 @@
#include "SkPoint.h"
#include "SkRect.h"
#include "SkImageDecoder.h"
-#include <Canvas.h>
#include <jni.h>
+#include <hwui/Canvas.h>
class SkBitmapRegionDecoder;
class SkCanvas;
namespace android {
class Paint;
-struct TypefaceImpl;
+struct Typeface;
}
class GraphicsJNI {
diff --git a/core/jni/android/graphics/Movie.cpp b/core/jni/android/graphics/Movie.cpp
index 498c590..71988f9c 100644
--- a/core/jni/android/graphics/Movie.cpp
+++ b/core/jni/android/graphics/Movie.cpp
@@ -1,7 +1,5 @@
-#include "Canvas.h"
#include "CreateJavaOutputStreamAdaptor.h"
#include "GraphicsJNI.h"
-#include "Paint.h"
#include "ScopedLocalRef.h"
#include "SkFrontBufferedStream.h"
#include "SkMovie.h"
@@ -12,6 +10,8 @@
#include <androidfw/Asset.h>
#include <androidfw/ResourceTypes.h>
+#include <hwui/Canvas.h>
+#include <hwui/Paint.h>
#include <jni.h>
#include <netinet/in.h>
diff --git a/core/jni/android/graphics/NinePatch.cpp b/core/jni/android/graphics/NinePatch.cpp
index 3ccbb35..4f2f389 100644
--- a/core/jni/android/graphics/NinePatch.cpp
+++ b/core/jni/android/graphics/NinePatch.cpp
@@ -19,12 +19,12 @@
#define LOG_NDEBUG 1
#include <androidfw/ResourceTypes.h>
+#include <hwui/Canvas.h>
+#include <hwui/Paint.h>
#include <utils/Log.h>
#include <ResourceCache.h>
-#include "Paint.h"
-#include "Canvas.h"
#include "SkCanvas.h"
#include "SkRegion.h"
#include "GraphicsJNI.h"
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index d00e94c..27d8fed 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -37,13 +37,13 @@
#include "unicode/ushape.h"
#include "utils/Blur.h"
+#include <hwui/MinikinSkia.h>
+#include <hwui/MinikinUtils.h>
+#include <hwui/Paint.h>
+#include <hwui/Typeface.h>
#include <minikin/GraphemeBreak.h>
#include <minikin/Measurement.h>
#include <unicode/utf16.h>
-#include "MinikinSkia.h"
-#include "MinikinUtils.h"
-#include "Paint.h"
-#include "TypefaceImpl.h"
#include <cassert>
#include <cstring>
@@ -402,8 +402,8 @@
const int kElegantDescent = -500;
const int kElegantLeading = 0;
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
- typeface = TypefaceImpl_resolveDefault(typeface);
+ Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle);
+ typeface = Typeface::resolveDefault(typeface);
FakedFont baseFont = typeface->fFontCollection->baseFontFaked(typeface->fStyle);
float saveSkewX = paint->getTextSkewX();
bool savefakeBold = paint->isFakeBoldText();
@@ -474,7 +474,7 @@
return descent - ascent + leading;
}
- static jfloat doTextAdvances(JNIEnv *env, Paint *paint, TypefaceImpl* typeface,
+ static jfloat doTextAdvances(JNIEnv *env, Paint *paint, Typeface* typeface,
const jchar *text, jint start, jint count, jint contextCount, jint bidiFlags,
jfloatArray advances, jint advancesIndex) {
NPE_CHECK_RETURN_ZERO(env, text);
@@ -510,7 +510,7 @@
jcharArray text, jint index, jint count, jint contextIndex, jint contextCount,
jint bidiFlags, jfloatArray advances, jint advancesIndex) {
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+ Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle);
jchar* textArray = env->GetCharArrayElements(text, NULL);
jfloat result = doTextAdvances(env, paint, typeface, textArray + contextIndex,
index - contextIndex, count, contextCount, bidiFlags, advances, advancesIndex);
@@ -523,7 +523,7 @@
jstring text, jint start, jint end, jint contextStart, jint contextEnd, jint bidiFlags,
jfloatArray advances, jint advancesIndex) {
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+ Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle);
const jchar* textArray = env->GetStringChars(text, NULL);
jfloat result = doTextAdvances(env, paint, typeface, textArray + contextStart,
start - contextStart, end - start, contextEnd - contextStart, bidiFlags,
@@ -590,7 +590,7 @@
SkPath tmpPath;
};
- static void getTextPath(JNIEnv* env, Paint* paint, TypefaceImpl* typeface, const jchar* text,
+ static void getTextPath(JNIEnv* env, Paint* paint, Typeface* typeface, const jchar* text,
jint count, jint bidiFlags, jfloat x, jfloat y, SkPath* path) {
Layout layout;
MinikinUtils::doLayout(&layout, paint, bidiFlags, typeface, text, 0, count, count);
@@ -613,7 +613,7 @@
jlong typefaceHandle, jint bidiFlags,
jcharArray text, jint index, jint count, jfloat x, jfloat y, jlong pathHandle) {
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+ Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle);
SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
const jchar* textArray = env->GetCharArrayElements(text, NULL);
getTextPath(env, paint, typeface, textArray + index, count, bidiFlags, x, y, path);
@@ -624,7 +624,7 @@
jlong typefaceHandle, jint bidiFlags,
jstring text, jint start, jint end, jfloat x, jfloat y, jlong pathHandle) {
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+ Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle);
SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
const jchar* textArray = env->GetStringChars(text, NULL);
getTextPath(env, paint, typeface, textArray + start, end - start, bidiFlags, x, y, path);
@@ -648,7 +648,7 @@
return paint->getLooper() && paint->getLooper()->asABlurShadow(NULL);
}
- static int breakText(JNIEnv* env, const Paint& paint, TypefaceImpl* typeface, const jchar text[],
+ static int breakText(JNIEnv* env, const Paint& paint, Typeface* typeface, const jchar text[],
int count, float maxWidth, jint bidiFlags, jfloatArray jmeasured,
const bool forwardScan) {
size_t measuredCount = 0;
@@ -685,7 +685,7 @@
NPE_CHECK_RETURN_ZERO(env, jtext);
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+ Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle);
bool forwardTextDirection;
if (count < 0) {
@@ -714,7 +714,7 @@
NPE_CHECK_RETURN_ZERO(env, jtext);
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+ Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle);
int count = env->GetStringLength(jtext);
const jchar* text = env->GetStringChars(jtext, NULL);
@@ -724,7 +724,7 @@
}
static void doTextBounds(JNIEnv* env, const jchar* text, int count, jobject bounds,
- const Paint& paint, TypefaceImpl* typeface, jint bidiFlags) {
+ const Paint& paint, Typeface* typeface, jint bidiFlags) {
SkRect r;
SkIRect ir;
@@ -743,7 +743,7 @@
static void getStringBounds(JNIEnv* env, jobject, jlong paintHandle, jlong typefaceHandle,
jstring text, jint start, jint end, jint bidiFlags, jobject bounds) {
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+ Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle);
const jchar* textArray = env->GetStringChars(text, NULL);
doTextBounds(env, textArray + start, end - start, bounds, *paint, typeface, bidiFlags);
env->ReleaseStringChars(text, textArray);
@@ -752,7 +752,7 @@
static void getCharArrayBounds(JNIEnv* env, jobject, jlong paintHandle, jlong typefaceHandle,
jcharArray text, jint index, jint count, jint bidiFlags, jobject bounds) {
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+ Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle);
const jchar* textArray = env->GetCharArrayElements(text, NULL);
doTextBounds(env, textArray + index, count, bounds, *paint, typeface, bidiFlags);
env->ReleaseCharArrayElements(text, const_cast<jchar*>(textArray),
@@ -771,7 +771,7 @@
static jboolean hasGlyph(JNIEnv *env, jclass, jlong paintHandle, jlong typefaceHandle,
jint bidiFlags, jstring string) {
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+ Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle);
ScopedStringChars str(env, string);
/* Start by rejecting unsupported base code point and variation selector pairs. */
@@ -820,7 +820,7 @@
return nGlyphs > 0 && !layoutContainsNotdef(layout);
}
- static jfloat doRunAdvance(const Paint* paint, TypefaceImpl* typeface, const jchar buf[],
+ static jfloat doRunAdvance(const Paint* paint, Typeface* typeface, const jchar buf[],
jint start, jint count, jint bufSize, jboolean isRtl, jint offset) {
int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR;
if (offset == count) {
@@ -837,7 +837,7 @@
jlong typefaceHandle, jcharArray text, jint start, jint end, jint contextStart,
jint contextEnd, jboolean isRtl, jint offset) {
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+ Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle);
jchar* textArray = (jchar*) env->GetPrimitiveArrayCritical(text, NULL);
jfloat result = doRunAdvance(paint, typeface, textArray + contextStart,
start - contextStart, end - start, contextEnd - contextStart, isRtl,
@@ -846,7 +846,7 @@
return result;
}
- static jint doOffsetForAdvance(const Paint* paint, TypefaceImpl* typeface, const jchar buf[],
+ static jint doOffsetForAdvance(const Paint* paint, Typeface* typeface, const jchar buf[],
jint start, jint count, jint bufSize, jboolean isRtl, jfloat advance) {
int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR;
std::unique_ptr<float[]> advancesArray(new float[count]);
@@ -859,7 +859,7 @@
jlong typefaceHandle, jcharArray text, jint start, jint end, jint contextStart,
jint contextEnd, jboolean isRtl, jfloat advance) {
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+ Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle);
jchar* textArray = (jchar*) env->GetPrimitiveArrayCritical(text, NULL);
jint result = doOffsetForAdvance(paint, typeface, textArray + contextStart,
start - contextStart, end - start, contextEnd - contextStart, isRtl, advance);
diff --git a/core/jni/android/graphics/Picture.cpp b/core/jni/android/graphics/Picture.cpp
index 6e83f1b..07e14a2 100644
--- a/core/jni/android/graphics/Picture.cpp
+++ b/core/jni/android/graphics/Picture.cpp
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-#include "Canvas.h"
#include "Picture.h"
#include "SkStream.h"
#include <memory>
+#include <hwui/Canvas.h>
namespace android {
diff --git a/core/jni/android/graphics/Rasterizer.cpp b/core/jni/android/graphics/Rasterizer.cpp
index a106ecf..3784f0d 100644
--- a/core/jni/android/graphics/Rasterizer.cpp
+++ b/core/jni/android/graphics/Rasterizer.cpp
@@ -22,10 +22,11 @@
#include "jni.h"
#include "GraphicsJNI.h"
-#include "Paint.h"
#include "SkLayerRasterizer.h"
#include "core_jni_helpers.h"
+#include <hwui/Paint.h>
+
// Rasterizer.java holds a pointer (jlong) to this guy
class NativeRasterizer {
public:
diff --git a/core/jni/android/graphics/Typeface.cpp b/core/jni/android/graphics/Typeface.cpp
index e97b768d..9a53cad 100644
--- a/core/jni/android/graphics/Typeface.cpp
+++ b/core/jni/android/graphics/Typeface.cpp
@@ -20,50 +20,57 @@
#include "GraphicsJNI.h"
#include "ScopedPrimitiveArray.h"
#include "SkTypeface.h"
-#include "TypefaceImpl.h"
#include <android_runtime/android_util_AssetManager.h>
#include <androidfw/AssetManager.h>
+#include <hwui/Typeface.h>
using namespace android;
static jlong Typeface_createFromTypeface(JNIEnv* env, jobject, jlong familyHandle, jint style) {
- TypefaceImpl* family = reinterpret_cast<TypefaceImpl*>(familyHandle);
- TypefaceImpl* face = TypefaceImpl_createFromTypeface(family, (SkTypeface::Style)style);
+ Typeface* family = reinterpret_cast<Typeface*>(familyHandle);
+ Typeface* face = Typeface::createFromTypeface(family, (SkTypeface::Style)style);
// TODO: the following logic shouldn't be necessary, the above should always succeed.
// Try to find the closest matching font, using the standard heuristic
if (NULL == face) {
- face = TypefaceImpl_createFromTypeface(family, (SkTypeface::Style)(style ^ SkTypeface::kItalic));
+ face = Typeface::createFromTypeface(family, (SkTypeface::Style)(style ^ SkTypeface::kItalic));
}
for (int i = 0; NULL == face && i < 4; i++) {
- face = TypefaceImpl_createFromTypeface(family, (SkTypeface::Style)i);
+ face = Typeface::createFromTypeface(family, (SkTypeface::Style)i);
}
return reinterpret_cast<jlong>(face);
}
static jlong Typeface_createWeightAlias(JNIEnv* env, jobject, jlong familyHandle, jint weight) {
- TypefaceImpl* family = reinterpret_cast<TypefaceImpl*>(familyHandle);
- TypefaceImpl* face = TypefaceImpl_createWeightAlias(family, weight);
+ Typeface* family = reinterpret_cast<Typeface*>(familyHandle);
+ Typeface* face = Typeface::createWeightAlias(family, weight);
return reinterpret_cast<jlong>(face);
}
static void Typeface_unref(JNIEnv* env, jobject obj, jlong faceHandle) {
- TypefaceImpl* face = reinterpret_cast<TypefaceImpl*>(faceHandle);
- TypefaceImpl_unref(face);
+ Typeface* face = reinterpret_cast<Typeface*>(faceHandle);
+ if (face != NULL) {
+ face->unref();
+ }
}
static jint Typeface_getStyle(JNIEnv* env, jobject obj, jlong faceHandle) {
- TypefaceImpl* face = reinterpret_cast<TypefaceImpl*>(faceHandle);
- return TypefaceImpl_getStyle(face);
+ Typeface* face = reinterpret_cast<Typeface*>(faceHandle);
+ return face->fSkiaStyle;
}
static jlong Typeface_createFromArray(JNIEnv *env, jobject, jlongArray familyArray) {
ScopedLongArrayRO families(env, familyArray);
- return reinterpret_cast<jlong>(TypefaceImpl_createFromFamilies(families.get(), families.size()));
+ std::vector<FontFamily*> familyVec;
+ for (size_t i = 0; i < families.size(); i++) {
+ FontFamily* family = reinterpret_cast<FontFamily*>(families[i]);
+ familyVec.push_back(family);
+ }
+ return reinterpret_cast<jlong>(Typeface::createFromFamilies(familyVec));
}
static void Typeface_setDefault(JNIEnv *env, jobject, jlong faceHandle) {
- TypefaceImpl* face = reinterpret_cast<TypefaceImpl*>(faceHandle);
- return TypefaceImpl_setDefault(face);
+ Typeface* face = reinterpret_cast<Typeface*>(faceHandle);
+ return Typeface::setDefault(face);
}
///////////////////////////////////////////////////////////////////////////////
diff --git a/core/jni/android/graphics/TypefaceImpl.h b/core/jni/android/graphics/TypefaceImpl.h
deleted file mode 100644
index 4b14917..0000000
--- a/core/jni/android/graphics/TypefaceImpl.h
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-#ifndef _ANDROID_GRAPHICS_TYPEFACE_IMPL_H_
-#define _ANDROID_GRAPHICS_TYPEFACE_IMPL_H_
-
-#include "jni.h" // for jlong, eventually remove
-#include "SkTypeface.h"
-#include <androidfw/AssetManager.h>
-
-#include <minikin/FontCollection.h>
-
-namespace android {
-
-struct TypefaceImpl {
- FontCollection *fFontCollection;
-
- // style used for constructing and querying Typeface objects
- SkTypeface::Style fSkiaStyle;
- // base weight in CSS-style units, 100..900
- int fBaseWeight;
-
- // resolved style actually used for rendering
- FontStyle fStyle;
-};
-
-// Note: it would be cleaner if the following functions were member
-// functions (static or otherwise) of the TypefaceImpl class. However,
-// that can't be easily accommodated in the case where TypefaceImpl
-// is just a pointer to SkTypeface, in the non-USE_MINIKIN case.
-// TODO: when #ifdef USE_MINIKIN is removed, move to member functions.
-
-TypefaceImpl* TypefaceImpl_resolveDefault(TypefaceImpl* src);
-
-TypefaceImpl* TypefaceImpl_createFromTypeface(TypefaceImpl* src, SkTypeface::Style style);
-
-TypefaceImpl* TypefaceImpl_createWeightAlias(TypefaceImpl* src, int baseweight);
-
-// When we remove the USE_MINIKIN ifdef, probably a good idea to move the casting
-// (from jlong to FontFamily*) to the caller in Typeface.cpp.
-TypefaceImpl* TypefaceImpl_createFromFamilies(const jlong* families, size_t size);
-
-void TypefaceImpl_unref(TypefaceImpl* face);
-
-int TypefaceImpl_getStyle(TypefaceImpl* face);
-
-void TypefaceImpl_setDefault(TypefaceImpl* face);
-
-}
-
-#endif // _ANDROID_GRAPHICS_TYPEFACE_IMPL_H_
diff --git a/core/jni/android/graphics/pdf/PdfDocument.cpp b/core/jni/android/graphics/pdf/PdfDocument.cpp
index 5d496e5..88e37e5 100644
--- a/core/jni/android/graphics/pdf/PdfDocument.cpp
+++ b/core/jni/android/graphics/pdf/PdfDocument.cpp
@@ -19,7 +19,6 @@
#include "core_jni_helpers.h"
#include <vector>
-#include "Canvas.h"
#include "CreateJavaOutputStreamAdaptor.h"
#include "SkDocument.h"
@@ -28,6 +27,8 @@
#include "SkStream.h"
#include "SkRect.h"
+#include <hwui/Canvas.h>
+
namespace android {
struct PageRecord {
diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp
index cf73316..ded4dac 100644
--- a/core/jni/android_graphics_Canvas.cpp
+++ b/core/jni/android_graphics_Canvas.cpp
@@ -19,15 +19,14 @@
#include "core_jni_helpers.h"
#include <androidfw/ResourceTypes.h>
-#include <Canvas.h>
+#include <hwui/Canvas.h>
+#include <hwui/Paint.h>
+#include <hwui/Typeface.h>
+#include <minikin/Layout.h>
#include "Bitmap.h"
#include "SkDrawFilter.h"
#include "SkGraphics.h"
-#include "Paint.h"
-#include "TypefaceImpl.h"
-
-#include "MinikinUtils.h"
namespace android {
@@ -475,111 +474,13 @@
vertA.ptr(), colorA.ptr(), paint);
}
-static void simplifyPaint(int color, SkPaint* paint) {
- paint->setColor(color);
- paint->setShader(nullptr);
- paint->setColorFilter(nullptr);
- paint->setLooper(nullptr);
- paint->setStrokeWidth(4 + 0.04 * paint->getTextSize());
- paint->setStrokeJoin(SkPaint::kRound_Join);
- paint->setLooper(nullptr);
-}
-
-class DrawTextFunctor {
-public:
- DrawTextFunctor(const Layout& layout, Canvas* canvas, uint16_t* glyphs, float* pos,
- const SkPaint& paint, float x, float y, MinikinRect& bounds,
- float totalAdvance)
- : layout(layout), canvas(canvas), glyphs(glyphs), pos(pos), paint(paint),
- x(x), y(y), bounds(bounds), totalAdvance(totalAdvance) { }
-
- void operator()(size_t start, size_t end) {
- if (canvas->drawTextAbsolutePos()) {
- for (size_t i = start; i < end; i++) {
- glyphs[i] = layout.getGlyphId(i);
- pos[2 * i] = x + layout.getX(i);
- pos[2 * i + 1] = y + layout.getY(i);
- }
- } else {
- for (size_t i = start; i < end; i++) {
- glyphs[i] = layout.getGlyphId(i);
- pos[2 * i] = layout.getX(i);
- pos[2 * i + 1] = layout.getY(i);
- }
- }
-
- size_t glyphCount = end - start;
-
- if (CC_UNLIKELY(canvas->isHighContrastText() && paint.getAlpha() != 0)) {
- // high contrast draw path
- int color = paint.getColor();
- int channelSum = SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color);
- bool darken = channelSum < (128 * 3);
-
- // outline
- SkPaint outlinePaint(paint);
- simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint);
- outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style);
- canvas->drawText(glyphs + start, pos + (2 * start), glyphCount, outlinePaint, x, y,
- bounds.mLeft, bounds.mTop, bounds.mRight, bounds.mBottom, totalAdvance);
-
- // inner
- SkPaint innerPaint(paint);
- simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint);
- innerPaint.setStyle(SkPaint::kFill_Style);
- canvas->drawText(glyphs + start, pos + (2 * start), glyphCount, innerPaint, x, y,
- bounds.mLeft, bounds.mTop, bounds.mRight, bounds.mBottom, totalAdvance);
- } else {
- // standard draw path
- canvas->drawText(glyphs + start, pos + (2 * start), glyphCount, paint, x, y,
- bounds.mLeft, bounds.mTop, bounds.mRight, bounds.mBottom,
- totalAdvance);
- }
- }
-private:
- const Layout& layout;
- Canvas* canvas;
- uint16_t* glyphs;
- float* pos;
- const SkPaint& paint;
- float x;
- float y;
- MinikinRect& bounds;
- float totalAdvance;
-};
-
-void drawText(Canvas* canvas, const uint16_t* text, int start, int count, int contextCount,
- float x, float y, int bidiFlags, const Paint& origPaint, TypefaceImpl* typeface) {
- // minikin may modify the original paint
- Paint paint(origPaint);
-
- Layout layout;
- MinikinUtils::doLayout(&layout, &paint, bidiFlags, typeface, text, start, count, contextCount);
-
- size_t nGlyphs = layout.nGlyphs();
- std::unique_ptr<uint16_t[]> glyphs(new uint16_t[nGlyphs]);
- std::unique_ptr<float[]> pos(new float[nGlyphs * 2]);
-
- x += MinikinUtils::xOffsetForTextAlign(&paint, layout);
-
- MinikinRect bounds;
- layout.getBounds(&bounds);
- if (!canvas->drawTextAbsolutePos()) {
- bounds.offset(x, y);
- }
-
- DrawTextFunctor f(layout, canvas, glyphs.get(), pos.get(),
- paint, x, y, bounds, layout.getAdvance());
- MinikinUtils::forFontRun(layout, &paint, f);
-}
-
static void drawTextChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray text,
jint index, jint count, jfloat x, jfloat y, jint bidiFlags,
jlong paintHandle, jlong typefaceHandle) {
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+ Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle);
jchar* jchars = env->GetCharArrayElements(text, NULL);
- drawText(get_canvas(canvasHandle), jchars + index, 0, count, count, x, y,
+ get_canvas(canvasHandle)->drawText(jchars + index, 0, count, count, x, y,
bidiFlags, *paint, typeface);
env->ReleaseCharArrayElements(text, jchars, JNI_ABORT);
}
@@ -588,10 +489,10 @@
jint start, jint end, jfloat x, jfloat y, jint bidiFlags,
jlong paintHandle, jlong typefaceHandle) {
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+ Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle);
const int count = end - start;
const jchar* jchars = env->GetStringChars(text, NULL);
- drawText(get_canvas(canvasHandle), jchars + start, 0, count, count, x, y,
+ get_canvas(canvasHandle)->drawText(jchars + start, 0, count, count, x, y,
bidiFlags, *paint, typeface);
env->ReleaseStringChars(text, jchars);
}
@@ -600,11 +501,11 @@
jint count, jint contextIndex, jint contextCount, jfloat x, jfloat y,
jboolean isRtl, jlong paintHandle, jlong typefaceHandle) {
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+ Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle);
const int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR;
jchar* jchars = env->GetCharArrayElements(text, NULL);
- drawText(get_canvas(canvasHandle), jchars + contextIndex, index - contextIndex, count,
+ get_canvas(canvasHandle)->drawText(jchars + contextIndex, index - contextIndex, count,
contextCount, x, y, bidiFlags, *paint, typeface);
env->ReleaseCharArrayElements(text, jchars, JNI_ABORT);
}
@@ -614,70 +515,28 @@
jfloat x, jfloat y, jboolean isRtl, jlong paintHandle,
jlong typefaceHandle) {
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+ Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle);
int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR;
jint count = end - start;
jint contextCount = contextEnd - contextStart;
const jchar* jchars = env->GetStringChars(text, NULL);
- drawText(get_canvas(canvasHandle), jchars + contextStart, start - contextStart, count,
+ get_canvas(canvasHandle)->drawText(jchars + contextStart, start - contextStart, count,
contextCount, x, y, bidiFlags, *paint, typeface);
env->ReleaseStringChars(text, jchars);
}
-class DrawTextOnPathFunctor {
-public:
- DrawTextOnPathFunctor(const Layout& layout, Canvas* canvas, float hOffset,
- float vOffset, const Paint& paint, const SkPath& path)
- : layout(layout), canvas(canvas), hOffset(hOffset), vOffset(vOffset),
- paint(paint), path(path) {
- }
- void operator()(size_t start, size_t end) {
- uint16_t glyphs[1];
- for (size_t i = start; i < end; i++) {
- glyphs[0] = layout.getGlyphId(i);
- float x = hOffset + layout.getX(i);
- float y = vOffset + layout.getY(i);
- canvas->drawTextOnPath(glyphs, 1, path, x, y, paint);
- }
- }
-private:
- const Layout& layout;
- Canvas* canvas;
- float hOffset;
- float vOffset;
- const Paint& paint;
- const SkPath& path;
-};
-
-static void drawTextOnPath(Canvas* canvas, const uint16_t* text, int count, int bidiFlags,
- const SkPath& path, float hOffset, float vOffset,
- const Paint& paint, TypefaceImpl* typeface) {
- Paint paintCopy(paint);
- Layout layout;
- MinikinUtils::doLayout(&layout, &paintCopy, bidiFlags, typeface, text, 0, count, count);
- hOffset += MinikinUtils::hOffsetForTextAlign(&paintCopy, layout, path);
-
- // Set align to left for drawing, as we don't want individual
- // glyphs centered or right-aligned; the offset above takes
- // care of all alignment.
- paintCopy.setTextAlign(Paint::kLeft_Align);
-
- DrawTextOnPathFunctor f(layout, canvas, hOffset, vOffset, paintCopy, path);
- MinikinUtils::forFontRun(layout, &paintCopy, f);
-}
-
static void drawTextOnPathChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray text,
jint index, jint count, jlong pathHandle, jfloat hOffset,
jfloat vOffset, jint bidiFlags, jlong paintHandle,
jlong typefaceHandle) {
SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+ Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle);
jchar* jchars = env->GetCharArrayElements(text, NULL);
- drawTextOnPath(get_canvas(canvasHandle), jchars + index, count, bidiFlags, *path,
+ get_canvas(canvasHandle)->drawTextOnPath(jchars + index, count, bidiFlags, *path,
hOffset, vOffset, *paint, typeface);
env->ReleaseCharArrayElements(text, jchars, 0);
@@ -688,12 +547,12 @@
jint bidiFlags, jlong paintHandle, jlong typefaceHandle) {
SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+ Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle);
const jchar* jchars = env->GetStringChars(text, NULL);
int count = env->GetStringLength(text);
- drawTextOnPath(get_canvas(canvasHandle), jchars, count, bidiFlags, *path,
+ get_canvas(canvasHandle)->drawTextOnPath(jchars, count, bidiFlags, *path,
hOffset, vOffset, *paint, typeface);
env->ReleaseStringChars(text, jchars);
diff --git a/core/jni/android_graphics_drawable_VectorDrawable.cpp b/core/jni/android_graphics_drawable_VectorDrawable.cpp
index e17de17..a2662f9 100644
--- a/core/jni/android_graphics_drawable_VectorDrawable.cpp
+++ b/core/jni/android_graphics_drawable_VectorDrawable.cpp
@@ -19,9 +19,10 @@
#include "core_jni_helpers.h"
#include "log/log.h"
-#include "Paint.h"
#include "VectorDrawable.h"
+#include <hwui/Paint.h>
+
namespace android {
using namespace uirenderer;
using namespace uirenderer::VectorDrawable;
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index 880a79c..2364787 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -41,7 +41,6 @@
extern "C" {
int ifc_enable(const char *ifname);
int ifc_disable(const char *ifname);
-int ifc_reset_connections(const char *ifname, int reset_mask);
}
#define NETUTILS_PKG_NAME "android/net/NetworkUtils"
@@ -50,21 +49,6 @@
static const uint16_t kDhcpClientPort = 68;
-static jint android_net_utils_resetConnections(JNIEnv* env, jobject clazz,
- jstring ifname, jint mask)
-{
- int result;
-
- const char *nameStr = env->GetStringUTFChars(ifname, NULL);
-
- ALOGD("android_net_utils_resetConnections in env=%p clazz=%p iface=%s mask=0x%x\n",
- env, clazz, nameStr, mask);
-
- result = ::ifc_reset_connections(nameStr, mask);
- env->ReleaseStringUTFChars(ifname, nameStr);
- return (jint)result;
-}
-
static void android_net_utils_attachDhcpFilter(JNIEnv *env, jobject clazz, jobject javaFd)
{
uint32_t ip_offset = sizeof(ether_header);
@@ -181,7 +165,6 @@
*/
static const JNINativeMethod gNetworkUtilMethods[] = {
/* name, signature, funcPtr */
- { "resetConnections", "(Ljava/lang/String;I)I", (void *)android_net_utils_resetConnections },
{ "bindProcessToNetwork", "(I)Z", (void*) android_net_utils_bindProcessToNetwork },
{ "getBoundNetworkForProcess", "()I", (void*) android_net_utils_getBoundNetworkForProcess },
{ "bindProcessToNetworkForHostResolution", "(I)Z", (void*) android_net_utils_bindProcessToNetworkForHostResolution },
diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp
index 83f76ea..13e4f1a 100644
--- a/core/jni/android_text_StaticLayout.cpp
+++ b/core/jni/android_text_StaticLayout.cpp
@@ -32,10 +32,12 @@
#include "SkPaint.h"
#include "SkTypeface.h"
-#include "MinikinSkia.h"
-#include "MinikinUtils.h"
-#include "Paint.h"
-#include "minikin/LineBreaker.h"
+#include <hwui/MinikinSkia.h>
+#include <hwui/MinikinUtils.h>
+#include <hwui/Paint.h>
+#include <minikin/FontCollection.h>
+#include <minikin/LineBreaker.h>
+#include <minikin/MinikinFont.h>
namespace android {
@@ -154,7 +156,7 @@
jlong nativePaint, jlong nativeTypeface, jint start, jint end, jboolean isRtl) {
LineBreaker* b = reinterpret_cast<LineBreaker*>(nativePtr);
Paint* paint = reinterpret_cast<Paint*>(nativePaint);
- TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(nativeTypeface);
+ Typeface* typeface = reinterpret_cast<Typeface*>(nativeTypeface);
FontCollection *font;
MinikinPaint minikinPaint;
FontStyle style = MinikinUtils::prepareMinikinPaint(&minikinPaint, &font, paint, typeface);
diff --git a/core/jni/android_view_DisplayListCanvas.cpp b/core/jni/android_view_DisplayListCanvas.cpp
index c87a770..6aac0e4 100644
--- a/core/jni/android_view_DisplayListCanvas.cpp
+++ b/core/jni/android_view_DisplayListCanvas.cpp
@@ -28,11 +28,11 @@
#include <SkRegion.h>
-#include <Canvas.h>
#include <Rect.h>
#include <RenderNode.h>
#include <CanvasProperty.h>
-#include <Paint.h>
+#include <hwui/Canvas.h>
+#include <hwui/Paint.h>
#include <renderthread/RenderProxy.h>
#include "core_jni_helpers.h"
diff --git a/core/jni/android_view_HardwareLayer.cpp b/core/jni/android_view_HardwareLayer.cpp
index 3a0ddc9..6b774e8 100644
--- a/core/jni/android_view_HardwareLayer.cpp
+++ b/core/jni/android_view_HardwareLayer.cpp
@@ -24,8 +24,8 @@
#include <android_runtime/android_graphics_SurfaceTexture.h>
#include <gui/GLConsumer.h>
+#include <hwui/Paint.h>
-#include <Paint.h>
#include <SkBitmap.h>
#include <SkCanvas.h>
#include <SkMatrix.h>
diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp
index a7ac5b8..79b518f 100644
--- a/core/jni/android_view_RenderNode.cpp
+++ b/core/jni/android_view_RenderNode.cpp
@@ -30,7 +30,7 @@
#include <RenderNode.h>
#include <renderthread/CanvasContext.h>
#include <TreeInfo.h>
-#include <Paint.h>
+#include <hwui/Paint.h>
#include "core_jni_helpers.h"
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 30607dd..60f05448 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -417,6 +417,7 @@
<protected-broadcast android:name="android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED" />
<protected-broadcast android:name="android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED" />
<protected-broadcast android:name="android.os.storage.action.VOLUME_STATE_CHANGED" />
+ <protected-broadcast android:name="android.os.storage.action.DISK_SCANNED" />
<protected-broadcast android:name="com.android.server.action.UPDATE_TWILIGHT_STATE" />
<protected-broadcast android:name="com.android.server.device_idle.STEP_IDLE_STATE" />
<protected-broadcast android:name="com.android.server.device_idle.STEP_LIGHT_IDLE_STATE" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index ba9bcaa..74ce753 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3368,8 +3368,8 @@
tapping/clicking the whole thing is going to do. -->
<string name="action_bar_home_subtitle_description_format">%1$s, %2$s, %3$s</string>
- <!-- Storage description for internal storage. [CHAR LIMIT=NONE] -->
- <string name="storage_internal">Internal storage</string>
+ <!-- Storage description for internal shared storage. [CHAR LIMIT=NONE] -->
+ <string name="storage_internal">Internal shared storage</string>
<!-- Storage description for a generic SD card. [CHAR LIMIT=NONE] -->
<string name="storage_sd_card">SD card</string>
diff --git a/core/tests/coretests/res/layout/remote_views_test.xml b/core/tests/coretests/res/layout/remote_views_test.xml
new file mode 100644
index 0000000..c5f7a47
--- /dev/null
+++ b/core/tests/coretests/res/layout/remote_views_test.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/layout"
+ android:orientation="vertical">
+
+ <TextView android:id="@+id/text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <ImageView android:id="@+id/image"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
diff --git a/core/tests/coretests/src/android/view/RemoteViewsTest.java b/core/tests/coretests/src/android/view/RemoteViewsTest.java
new file mode 100644
index 0000000..9c4fd70
--- /dev/null
+++ b/core/tests/coretests/src/android/view/RemoteViewsTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.view;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import com.android.frameworks.coretests.R;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+/**
+ * Tests for RemoteViews.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RemoteViewsTest {
+
+ @Rule
+ public final ExpectedException exception = ExpectedException.none();
+
+ private Context mContext;
+ private String mPackage;
+ private LinearLayout mContainer;
+
+ @Before
+ public void setup() {
+ mContext = InstrumentationRegistry.getContext();
+ mPackage = mPackage;
+ mContainer = new LinearLayout(mContext);
+ }
+
+ @Test
+ public void clone_doesNotCopyBitmap() {
+ RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test);
+ Bitmap bitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
+
+ original.setImageViewBitmap(R.id.image, bitmap);
+ RemoteViews clone = original.clone();
+ View inflated = clone.apply(mContext, mContainer);
+
+ Drawable drawable = ((ImageView) inflated.findViewById(R.id.image)).getDrawable();
+ assertSame(bitmap, ((BitmapDrawable)drawable).getBitmap());
+ }
+
+ @Test
+ public void clone_originalCanStillBeApplied() {
+ RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test);
+
+ RemoteViews clone = original.clone();
+
+ clone.apply(mContext, mContainer);
+ }
+
+ @Test
+ public void clone_clones() {
+ RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test);
+
+ RemoteViews clone = original.clone();
+ original.setTextViewText(R.id.text, "test");
+ View inflated = clone.apply(mContext, mContainer);
+
+ TextView textView = (TextView) inflated.findViewById(R.id.text);
+ assertEquals("", textView.getText());
+ }
+
+ @Test
+ public void clone_child_fails() {
+ RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test);
+ RemoteViews child = new RemoteViews(mPackage, R.layout.remote_views_test);
+
+ original.addView(R.id.layout, child);
+
+ exception.expect(IllegalStateException.class);
+ RemoteViews clone = child.clone();
+ }
+
+ @Test
+ public void clone_repeatedly() {
+ RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test);
+
+ original.clone();
+ original.clone();
+
+ original.apply(mContext, mContainer);
+ }
+
+ @Test
+ public void clone_chained() {
+ RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test);
+
+ RemoteViews clone = original.clone().clone();
+
+ clone.apply(mContext, mContainer);
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
index 923b829..3fbc16a 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
@@ -44,6 +44,7 @@
import android.support.test.espresso.Espresso;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Suppress;
import android.view.MotionEvent;
import android.widget.espresso.ContextMenuUtils;
@@ -97,6 +98,7 @@
}
@SmallTest
+ @Suppress
public void testContextMenu() throws Exception {
final String text = "abc def ghi.";
onView(withId(R.id.textview)).perform(click());
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index ca07738..516591b 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -12,6 +12,11 @@
hwui_src_files := \
font/CacheTexture.cpp \
font/Font.cpp \
+ hwui/Canvas.cpp \
+ hwui/MinikinSkia.cpp \
+ hwui/MinikinUtils.cpp \
+ hwui/PaintImpl.cpp \
+ hwui/Typeface.cpp \
renderstate/Blend.cpp \
renderstate/MeshState.cpp \
renderstate/OffscreenBufferPool.cpp \
@@ -41,7 +46,6 @@
AnimatorManager.cpp \
AssetAtlas.cpp \
Caches.cpp \
- Canvas.cpp \
CanvasState.cpp \
ClipArea.cpp \
DamageAccumulator.cpp \
@@ -143,7 +147,9 @@
hwui_c_includes += \
external/skia/include/private \
- external/skia/src/core
+ external/skia/src/core \
+ external/harfbuzz_ng/src \
+ external/freetype/include
ifneq (false,$(ANDROID_ENABLE_RENDERSCRIPT))
hwui_cflags += -DANDROID_ENABLE_RENDERSCRIPT
diff --git a/libs/hwui/BakedOpState.cpp b/libs/hwui/BakedOpState.cpp
index 26653f7..85903654 100644
--- a/libs/hwui/BakedOpState.cpp
+++ b/libs/hwui/BakedOpState.cpp
@@ -82,6 +82,16 @@
}
}
+ResolvedRenderState::ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot,
+ const Matrix4& localTransform, const ClipBase* localClip) {
+ transform.loadMultiply(*snapshot.transform, localTransform);
+ clipState = snapshot.mutateClipArea().serializeIntersectedClip(allocator,
+ localClip, *(snapshot.transform));
+ clippedBounds = clipState->rect;
+ clipSideFlags = OpClipSideFlags::Full;
+ localProjectionPathMask = nullptr;
+}
+
ResolvedRenderState::ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot)
: transform(*snapshot.transform)
, clipState(snapshot.mutateClipArea().serializeClip(allocator))
diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h
index ffe2901..4e3cb8a 100644
--- a/libs/hwui/BakedOpState.h
+++ b/libs/hwui/BakedOpState.h
@@ -55,6 +55,10 @@
ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot,
const RecordedOp& recordedOp, bool expandForStroke);
+ // Constructor for unbounded ops *with* transform/clip
+ ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot,
+ const Matrix4& localTransform, const ClipBase* localClip);
+
// Constructor for unbounded ops without transform/clip (namely shadows)
ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot);
@@ -111,8 +115,14 @@
return bakedState;
}
+ static BakedOpState* tryConstructUnbounded(LinearAllocator& allocator,
+ Snapshot& snapshot, const RecordedOp& recordedOp) {
+ if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
+ return allocator.create_trivial<BakedOpState>(allocator, snapshot, recordedOp);
+ }
+
enum class StrokeBehavior {
- // stroking is forced, regardless of style on paint
+ // stroking is forced, regardless of style on paint (such as for lines)
Forced,
// stroking is defined by style on paint
StyleDefined,
@@ -167,6 +177,13 @@
, roundRectClipState(snapshot.roundRectClipState)
, op(&recordedOp) {}
+ // TODO: fix this brittleness
+ BakedOpState(LinearAllocator& allocator, Snapshot& snapshot, const RecordedOp& recordedOp)
+ : computedState(allocator, snapshot, recordedOp.localMatrix, recordedOp.localClip)
+ , alpha(snapshot.alpha)
+ , roundRectClipState(snapshot.roundRectClipState)
+ , op(&recordedOp) {}
+
BakedOpState(LinearAllocator& allocator, Snapshot& snapshot, const ShadowOp* shadowOpPtr)
: computedState(allocator, snapshot)
, alpha(snapshot.alpha)
diff --git a/libs/hwui/Canvas.cpp b/libs/hwui/Canvas.cpp
deleted file mode 100644
index 11ae1a1..0000000
--- a/libs/hwui/Canvas.cpp
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "Canvas.h"
-
-#include "DisplayListCanvas.h"
-#include "RecordingCanvas.h"
-#include <SkDrawFilter.h>
-
-namespace android {
-
-Canvas* Canvas::create_recording_canvas(int width, int height) {
-#if HWUI_NEW_OPS
- return new uirenderer::RecordingCanvas(width, height);
-#else
- return new uirenderer::DisplayListCanvas(width, height);
-#endif
-}
-
-void Canvas::drawTextDecorations(float x, float y, float length, const SkPaint& paint) {
- uint32_t flags;
- SkDrawFilter* drawFilter = getDrawFilter();
- if (drawFilter) {
- SkPaint paintCopy(paint);
- drawFilter->filter(&paintCopy, SkDrawFilter::kText_Type);
- flags = paintCopy.getFlags();
- } else {
- flags = paint.getFlags();
- }
- if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) {
- // Same values used by Skia
- const float kStdStrikeThru_Offset = (-6.0f / 21.0f);
- const float kStdUnderline_Offset = (1.0f / 9.0f);
- const float kStdUnderline_Thickness = (1.0f / 18.0f);
-
- SkScalar left = x;
- SkScalar right = x + length;
- float textSize = paint.getTextSize();
- float strokeWidth = fmax(textSize * kStdUnderline_Thickness, 1.0f);
- if (flags & SkPaint::kUnderlineText_Flag) {
- SkScalar top = y + textSize * kStdUnderline_Offset - 0.5f * strokeWidth;
- SkScalar bottom = y + textSize * kStdUnderline_Offset + 0.5f * strokeWidth;
- drawRect(left, top, right, bottom, paint);
- }
- if (flags & SkPaint::kStrikeThruText_Flag) {
- SkScalar top = y + textSize * kStdStrikeThru_Offset - 0.5f * strokeWidth;
- SkScalar bottom = y + textSize * kStdStrikeThru_Offset + 0.5f * strokeWidth;
- drawRect(left, top, right, bottom, paint);
- }
- }
-}
-
-} // namespace android
diff --git a/libs/hwui/CanvasState.cpp b/libs/hwui/CanvasState.cpp
index 43ff33f..e2149d1 100644
--- a/libs/hwui/CanvasState.cpp
+++ b/libs/hwui/CanvasState.cpp
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#include "Canvas.h"
#include "CanvasState.h"
+#include "hwui/Canvas.h"
#include "utils/MathUtils.h"
namespace android {
diff --git a/libs/hwui/DisplayListCanvas.cpp b/libs/hwui/DisplayListCanvas.cpp
index a14bdc4..2dccf32 100644
--- a/libs/hwui/DisplayListCanvas.cpp
+++ b/libs/hwui/DisplayListCanvas.cpp
@@ -418,7 +418,7 @@
addDrawOp(new (alloc()) DrawVectorDrawableOp(tree));
}
-void DisplayListCanvas::drawTextOnPath(const uint16_t* glyphs, int count,
+void DisplayListCanvas::drawGlyphsOnPath(const uint16_t* glyphs, int count,
const SkPath& path, float hOffset, float vOffset, const SkPaint& paint) {
if (!glyphs || count <= 0) return;
@@ -429,7 +429,7 @@
addDrawOp(op);
}
-void DisplayListCanvas::drawText(const uint16_t* glyphs, const float* positions,
+void DisplayListCanvas::drawGlyphs(const uint16_t* glyphs, const float* positions,
int count, const SkPaint& paint, float x, float y,
float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
float totalAdvance) {
diff --git a/libs/hwui/DisplayListCanvas.h b/libs/hwui/DisplayListCanvas.h
index a703e22..d6a5794 100644
--- a/libs/hwui/DisplayListCanvas.h
+++ b/libs/hwui/DisplayListCanvas.h
@@ -17,12 +17,12 @@
#ifndef ANDROID_HWUI_DISPLAY_LIST_RENDERER_H
#define ANDROID_HWUI_DISPLAY_LIST_RENDERER_H
-#include "Canvas.h"
#include "CanvasState.h"
#include "DisplayList.h"
#include "RenderNode.h"
#include "ResourceCache.h"
#include "SkiaCanvasProxy.h"
+#include "hwui/Canvas.h"
#include "utils/Macros.h"
#include <SkDrawFilter.h>
@@ -209,10 +209,10 @@
virtual void drawVectorDrawable(VectorDrawableRoot* tree) override;
// Text
- virtual void drawText(const uint16_t* glyphs, const float* positions, int count,
+ virtual void drawGlyphs(const uint16_t* glyphs, const float* positions, int count,
const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop,
float boundsRight, float boundsBottom, float totalAdvance) override;
- virtual void drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
+ virtual void drawGlyphsOnPath(const uint16_t* glyphs, int count, const SkPath& path,
float hOffset, float vOffset, const SkPaint& paint) override;
virtual bool drawTextAbsolutePos() const override { return false; }
diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp
index 50b21a4..dc967e0 100644
--- a/libs/hwui/FrameBuilder.cpp
+++ b/libs/hwui/FrameBuilder.cpp
@@ -16,11 +16,11 @@
#include "FrameBuilder.h"
-#include "Canvas.h"
#include "LayerUpdateQueue.h"
#include "RenderNode.h"
#include "VectorDrawable.h"
#include "renderstate/OffscreenBufferPool.h"
+#include "hwui/Canvas.h"
#include "utils/FatVector.h"
#include "utils/PaintUtils.h"
#include "utils/TraceUtils.h"
@@ -574,7 +574,7 @@
}
void FrameBuilder::deferFunctorOp(const FunctorOp& op) {
- BakedOpState* bakedState = tryBakeOpState(op);
+ BakedOpState* bakedState = tryBakeUnboundedOpState(op);
if (!bakedState) return; // quick rejected
currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Functor);
}
@@ -663,7 +663,7 @@
}
void FrameBuilder::deferTextOnPathOp(const TextOnPathOp& op) {
- BakedOpState* bakedState = tryBakeOpState(op);
+ BakedOpState* bakedState = tryBakeUnboundedOpState(op);
if (!bakedState) return; // quick rejected
currentLayer().deferUnmergeableOp(mAllocator, bakedState, textBatchId(*(op.paint)));
}
diff --git a/libs/hwui/FrameBuilder.h b/libs/hwui/FrameBuilder.h
index f44306a..8a00d33 100644
--- a/libs/hwui/FrameBuilder.h
+++ b/libs/hwui/FrameBuilder.h
@@ -173,6 +173,10 @@
BakedOpState* tryBakeOpState(const RecordedOp& recordedOp) {
return BakedOpState::tryConstruct(mAllocator, *mCanvasState.writableSnapshot(), recordedOp);
}
+ BakedOpState* tryBakeUnboundedOpState(const RecordedOp& recordedOp) {
+ return BakedOpState::tryConstructUnbounded(mAllocator, *mCanvasState.writableSnapshot(), recordedOp);
+ }
+
// should always be surrounded by a save/restore pair, and not called if DisplayList is null
void deferNodePropsAndOps(RenderNode& node);
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 7693fdc..c099427 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -17,7 +17,6 @@
#include <GpuMemoryTracker.h>
#include "OpenGLRenderer.h"
-#include "Canvas.h"
#include "DeferredDisplayList.h"
#include "GammaFontRenderer.h"
#include "Glop.h"
@@ -32,6 +31,7 @@
#include "SkiaShader.h"
#include "Vector.h"
#include "VertexBuffer.h"
+#include "hwui/Canvas.h"
#include "utils/GLUtils.h"
#include "utils/PaintUtils.h"
#include "utils/TraceUtils.h"
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index c37458d..96a57b6 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -257,8 +257,10 @@
};
struct FunctorOp : RecordedOp {
- FunctorOp(BASE_PARAMS_PAINTLESS, Functor* functor)
- : SUPER_PAINTLESS(FunctorOp)
+ // Note: undefined record-time bounds, since this op fills the clip
+ // TODO: explicitly define bounds
+ FunctorOp(const Matrix4& localMatrix, const ClipBase* localClip, Functor* functor)
+ : RecordedOp(RecordedOpId::FunctorOp, Rect(), localMatrix, localClip, nullptr)
, functor(functor) {}
Functor* functor;
};
@@ -385,9 +387,10 @@
};
struct TextOnPathOp : RecordedOp {
- TextOnPathOp(BASE_PARAMS, const glyph_t* glyphs, int glyphCount,
- const SkPath* path, float hOffset, float vOffset)
- : SUPER(TextOnPathOp)
+ // TODO: explicitly define bounds
+ TextOnPathOp(const Matrix4& localMatrix, const ClipBase* localClip, const SkPaint* paint,
+ const glyph_t* glyphs, int glyphCount, const SkPath* path, float hOffset, float vOffset)
+ : RecordedOp(RecordedOpId::TextOnPathOp, Rect(), localMatrix, localClip, paint)
, glyphs(glyphs)
, glyphCount(glyphCount)
, path(path)
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 11eb825..cf78781 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -24,14 +24,6 @@
namespace android {
namespace uirenderer {
-#define MIL_PIX 1000000
-static Rect sUnreasonablyLargeBounds(-MIL_PIX, -MIL_PIX, MIL_PIX, MIL_PIX);
-
-static const Rect& getConservativeOpBounds(const ClipBase* clip) {
- // if op is clipped, that rect can be used, but otherwise just use a conservatively large rect
- return clip ? clip->rect : sUnreasonablyLargeBounds;
-}
-
RecordingCanvas::RecordingCanvas(size_t width, size_t height)
: mState(*this)
, mResourceCache(ResourceCache::getInstance()) {
@@ -249,12 +241,10 @@
}
void RecordingCanvas::drawPaint(const SkPaint& paint) {
- const ClipBase* clip = getRecordedClip();
- addOp(alloc().create_trivial<RectOp>(
- getConservativeOpBounds(clip),
- Matrix4::identity(),
- clip,
- refPaint(&paint)));
+ SkRect bounds;
+ if (getClipBounds(&bounds)) {
+ drawRect(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, paint);
+ }
}
static Rect calcBoundsOfPoints(const float* points, int floatCount) {
@@ -524,7 +514,7 @@
}
// Text
-void RecordingCanvas::drawText(const uint16_t* glyphs, const float* positions, int glyphCount,
+void RecordingCanvas::drawGlyphs(const uint16_t* glyphs, const float* positions, int glyphCount,
const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop,
float boundsRight, float boundsBottom, float totalAdvance) {
if (!glyphs || !positions || glyphCount <= 0 || PaintUtils::paintWillNotDrawText(paint)) return;
@@ -540,15 +530,13 @@
drawTextDecorations(x, y, totalAdvance, paint);
}
-void RecordingCanvas::drawTextOnPath(const uint16_t* glyphs, int glyphCount, const SkPath& path,
+void RecordingCanvas::drawGlyphsOnPath(const uint16_t* glyphs, int glyphCount, const SkPath& path,
float hOffset, float vOffset, const SkPaint& paint) {
if (!glyphs || glyphCount <= 0 || PaintUtils::paintWillNotDrawText(paint)) return;
glyphs = refBuffer<glyph_t>(glyphs, glyphCount);
- auto clip = getRecordedClip();
addOp(alloc().create_trivial<TextOnPathOp>(
- getConservativeOpBounds(clip), // TODO: explicitly define bounds
*(mState.currentSnapshot()->transform),
- clip,
+ getRecordedClip(),
refPaint(&paint), glyphs, glyphCount, refPath(&path), hOffset, vOffset));
}
@@ -599,11 +587,9 @@
void RecordingCanvas::callDrawGLFunction(Functor* functor) {
mDisplayList->functors.push_back(functor);
- auto clip = getRecordedClip();
addOp(alloc().create_trivial<FunctorOp>(
- getConservativeOpBounds(clip),
*(mState.currentSnapshot()->transform),
- clip,
+ getRecordedClip(),
functor));
}
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 719872d..1eb4fa0 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -17,12 +17,12 @@
#ifndef ANDROID_HWUI_RECORDING_CANVAS_H
#define ANDROID_HWUI_RECORDING_CANVAS_H
-#include "Canvas.h"
#include "CanvasState.h"
#include "DisplayList.h"
#include "ResourceCache.h"
#include "SkiaCanvasProxy.h"
#include "Snapshot.h"
+#include "hwui/Canvas.h"
#include "utils/LinearAllocator.h"
#include "utils/Macros.h"
#include "utils/NinePatch.h"
@@ -191,10 +191,10 @@
const SkPaint* paint) override;
// Text
- virtual void drawText(const uint16_t* glyphs, const float* positions, int glyphCount,
+ virtual void drawGlyphs(const uint16_t* glyphs, const float* positions, int glyphCount,
const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop,
float boundsRight, float boundsBottom, float totalAdvance) override;
- virtual void drawTextOnPath(const uint16_t* glyphs, int glyphCount, const SkPath& path,
+ virtual void drawGlyphsOnPath(const uint16_t* glyphs, int glyphCount, const SkPath& path,
float hOffset, float vOffset, const SkPaint& paint) override;
virtual bool drawTextAbsolutePos() const override { return false; }
diff --git a/libs/hwui/RenderProperties.cpp b/libs/hwui/RenderProperties.cpp
index 0b0f0fa..f577785 100644
--- a/libs/hwui/RenderProperties.cpp
+++ b/libs/hwui/RenderProperties.cpp
@@ -23,9 +23,9 @@
#include <SkPath.h>
#include <SkPathOps.h>
-#include "Canvas.h"
#include "Matrix.h"
#include "OpenGLRenderer.h"
+#include "hwui/Canvas.h"
#include "utils/MathUtils.h"
namespace android {
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index bd4442d..b1ecb71 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-#include "Canvas.h"
#include "CanvasProperty.h"
#include "Layer.h"
#include "RenderNode.h"
+#include "hwui/Canvas.h"
#include <SkCanvas.h>
#include <SkClipStack.h>
@@ -147,11 +147,11 @@
float dstLeft, float dstTop, float dstRight, float dstBottom,
const SkPaint* paint) override;
- virtual void drawText(const uint16_t* text, const float* positions, int count,
+ virtual void drawGlyphs(const uint16_t* text, const float* positions, int count,
const SkPaint& paint, float x, float y,
float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
float totalAdvance) override;
- virtual void drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
+ virtual void drawGlyphsOnPath(const uint16_t* glyphs, int count, const SkPath& path,
float hOffset, float vOffset, const SkPaint& paint) override;
virtual bool drawTextAbsolutePos() const override { return true; }
@@ -757,7 +757,7 @@
// Canvas draw operations: Text
// ----------------------------------------------------------------------------
-void SkiaCanvas::drawText(const uint16_t* text, const float* positions, int count,
+void SkiaCanvas::drawGlyphs(const uint16_t* text, const float* positions, int count,
const SkPaint& paint, float x, float y,
float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
float totalAdvance) {
@@ -772,7 +772,7 @@
drawTextDecorations(x, y, totalAdvance, paint);
}
-void SkiaCanvas::drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
+void SkiaCanvas::drawGlyphsOnPath(const uint16_t* glyphs, int count, const SkPath& path,
float hOffset, float vOffset, const SkPaint& paint) {
mCanvas->drawTextOnPathHV(glyphs, count << 1, path, hOffset, vOffset, paint);
}
diff --git a/libs/hwui/SkiaCanvasProxy.cpp b/libs/hwui/SkiaCanvasProxy.cpp
index 6530d4ed8..c612480 100644
--- a/libs/hwui/SkiaCanvasProxy.cpp
+++ b/libs/hwui/SkiaCanvasProxy.cpp
@@ -290,7 +290,7 @@
}
static_assert(sizeof(SkPoint) == sizeof(float)*2, "SkPoint is no longer two floats");
- mCanvas->drawText(glyphs.glyphIDs, &pointStorage[0].fX, glyphs.count, glyphs.paint,
+ mCanvas->drawGlyphs(glyphs.glyphIDs, &pointStorage[0].fX, glyphs.count, glyphs.paint,
x, y, bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, 0);
}
@@ -326,7 +326,7 @@
bounds.offset(x, y);
static_assert(sizeof(SkPoint) == sizeof(float)*2, "SkPoint is no longer two floats");
- mCanvas->drawText(glyphs.glyphIDs, &posArray[0].fX, glyphs.count, glyphs.paint, x, y,
+ mCanvas->drawGlyphs(glyphs.glyphIDs, &posArray[0].fX, glyphs.count, glyphs.paint, x, y,
bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, 0);
}
@@ -344,7 +344,7 @@
const SkMatrix* matrix, const SkPaint& origPaint) {
// convert to glyphIDs if necessary
GlyphIDConverter glyphs(text, byteLength, origPaint);
- mCanvas->drawTextOnPath(glyphs.glyphIDs, glyphs.count, path, 0, 0, glyphs.paint);
+ mCanvas->drawGlyphsOnPath(glyphs.glyphIDs, glyphs.count, path, 0, 0, glyphs.paint);
}
void SkiaCanvasProxy::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
diff --git a/libs/hwui/SkiaCanvasProxy.h b/libs/hwui/SkiaCanvasProxy.h
index e342d192..973c55f 100644
--- a/libs/hwui/SkiaCanvasProxy.h
+++ b/libs/hwui/SkiaCanvasProxy.h
@@ -20,7 +20,7 @@
#include <cutils/compiler.h>
#include <SkCanvas.h>
-#include "Canvas.h"
+#include "hwui/Canvas.h"
namespace android {
namespace uirenderer {
diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp
index cf5e69a..d784280 100644
--- a/libs/hwui/Snapshot.cpp
+++ b/libs/hwui/Snapshot.cpp
@@ -16,7 +16,7 @@
#include "Snapshot.h"
-#include "Canvas.h"
+#include "hwui/Canvas.h"
namespace android {
namespace uirenderer {
diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h
index 4d2fed0..7a45bf5 100644
--- a/libs/hwui/VectorDrawable.h
+++ b/libs/hwui/VectorDrawable.h
@@ -17,7 +17,7 @@
#ifndef ANDROID_HWUI_VPATH_H
#define ANDROID_HWUI_VPATH_H
-#include "Canvas.h"
+#include "hwui/Canvas.h"
#include <SkBitmap.h>
#include <SkColor.h>
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
new file mode 100644
index 0000000..8c3eea3
--- /dev/null
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Canvas.h"
+
+#include "DisplayListCanvas.h"
+#include "RecordingCanvas.h"
+#include "MinikinUtils.h"
+#include "Paint.h"
+#include "Typeface.h"
+
+#include <SkDrawFilter.h>
+
+namespace android {
+
+Canvas* Canvas::create_recording_canvas(int width, int height) {
+#if HWUI_NEW_OPS
+ return new uirenderer::RecordingCanvas(width, height);
+#else
+ return new uirenderer::DisplayListCanvas(width, height);
+#endif
+}
+
+void Canvas::drawTextDecorations(float x, float y, float length, const SkPaint& paint) {
+ uint32_t flags;
+ SkDrawFilter* drawFilter = getDrawFilter();
+ if (drawFilter) {
+ SkPaint paintCopy(paint);
+ drawFilter->filter(&paintCopy, SkDrawFilter::kText_Type);
+ flags = paintCopy.getFlags();
+ } else {
+ flags = paint.getFlags();
+ }
+ if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) {
+ // Same values used by Skia
+ const float kStdStrikeThru_Offset = (-6.0f / 21.0f);
+ const float kStdUnderline_Offset = (1.0f / 9.0f);
+ const float kStdUnderline_Thickness = (1.0f / 18.0f);
+
+ SkScalar left = x;
+ SkScalar right = x + length;
+ float textSize = paint.getTextSize();
+ float strokeWidth = fmax(textSize * kStdUnderline_Thickness, 1.0f);
+ if (flags & SkPaint::kUnderlineText_Flag) {
+ SkScalar top = y + textSize * kStdUnderline_Offset - 0.5f * strokeWidth;
+ SkScalar bottom = y + textSize * kStdUnderline_Offset + 0.5f * strokeWidth;
+ drawRect(left, top, right, bottom, paint);
+ }
+ if (flags & SkPaint::kStrikeThruText_Flag) {
+ SkScalar top = y + textSize * kStdStrikeThru_Offset - 0.5f * strokeWidth;
+ SkScalar bottom = y + textSize * kStdStrikeThru_Offset + 0.5f * strokeWidth;
+ drawRect(left, top, right, bottom, paint);
+ }
+ }
+}
+
+static void simplifyPaint(int color, SkPaint* paint) {
+ paint->setColor(color);
+ paint->setShader(nullptr);
+ paint->setColorFilter(nullptr);
+ paint->setLooper(nullptr);
+ paint->setStrokeWidth(4 + 0.04 * paint->getTextSize());
+ paint->setStrokeJoin(SkPaint::kRound_Join);
+ paint->setLooper(nullptr);
+}
+
+class DrawTextFunctor {
+public:
+ DrawTextFunctor(const Layout& layout, Canvas* canvas, uint16_t* glyphs, float* pos,
+ const SkPaint& paint, float x, float y, MinikinRect& bounds, float totalAdvance)
+ : layout(layout)
+ , canvas(canvas)
+ , glyphs(glyphs)
+ , pos(pos)
+ , paint(paint)
+ , x(x)
+ , y(y)
+ , bounds(bounds)
+ , totalAdvance(totalAdvance) {
+ }
+
+ void operator()(size_t start, size_t end) {
+ if (canvas->drawTextAbsolutePos()) {
+ for (size_t i = start; i < end; i++) {
+ glyphs[i] = layout.getGlyphId(i);
+ pos[2 * i] = x + layout.getX(i);
+ pos[2 * i + 1] = y + layout.getY(i);
+ }
+ } else {
+ for (size_t i = start; i < end; i++) {
+ glyphs[i] = layout.getGlyphId(i);
+ pos[2 * i] = layout.getX(i);
+ pos[2 * i + 1] = layout.getY(i);
+ }
+ }
+
+ size_t glyphCount = end - start;
+
+ if (CC_UNLIKELY(canvas->isHighContrastText() && paint.getAlpha() != 0)) {
+ // high contrast draw path
+ int color = paint.getColor();
+ int channelSum = SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color);
+ bool darken = channelSum < (128 * 3);
+
+ // outline
+ SkPaint outlinePaint(paint);
+ simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint);
+ outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style);
+ canvas->drawGlyphs(glyphs + start, pos + (2 * start), glyphCount, outlinePaint, x, y,
+ bounds.mLeft, bounds.mTop, bounds.mRight, bounds.mBottom, totalAdvance);
+
+ // inner
+ SkPaint innerPaint(paint);
+ simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint);
+ innerPaint.setStyle(SkPaint::kFill_Style);
+ canvas->drawGlyphs(glyphs + start, pos + (2 * start), glyphCount, innerPaint, x, y,
+ bounds.mLeft, bounds.mTop, bounds.mRight, bounds.mBottom, totalAdvance);
+ } else {
+ // standard draw path
+ canvas->drawGlyphs(glyphs + start, pos + (2 * start), glyphCount, paint, x, y,
+ bounds.mLeft, bounds.mTop, bounds.mRight, bounds.mBottom, totalAdvance);
+ }
+ }
+private:
+ const Layout& layout;
+ Canvas* canvas;
+ uint16_t* glyphs;
+ float* pos;
+ const SkPaint& paint;
+ float x;
+ float y;
+ MinikinRect& bounds;
+ float totalAdvance;
+};
+
+void Canvas::drawText(const uint16_t* text, int start, int count, int contextCount,
+ float x, float y, int bidiFlags, const Paint& origPaint, Typeface* typeface) {
+ // minikin may modify the original paint
+ Paint paint(origPaint);
+
+ Layout layout;
+ MinikinUtils::doLayout(&layout, &paint, bidiFlags, typeface, text, start, count, contextCount);
+
+ size_t nGlyphs = layout.nGlyphs();
+ std::unique_ptr<uint16_t[]> glyphs(new uint16_t[nGlyphs]);
+ std::unique_ptr<float[]> pos(new float[nGlyphs * 2]);
+
+ x += MinikinUtils::xOffsetForTextAlign(&paint, layout);
+
+ MinikinRect bounds;
+ layout.getBounds(&bounds);
+ if (!drawTextAbsolutePos()) {
+ bounds.offset(x, y);
+ }
+
+ DrawTextFunctor f(layout, this, glyphs.get(), pos.get(),
+ paint, x, y, bounds, layout.getAdvance());
+ MinikinUtils::forFontRun(layout, &paint, f);
+}
+
+class DrawTextOnPathFunctor {
+public:
+ DrawTextOnPathFunctor(const Layout& layout, Canvas* canvas, float hOffset,
+ float vOffset, const Paint& paint, const SkPath& path)
+ : layout(layout)
+ , canvas(canvas)
+ , hOffset(hOffset)
+ , vOffset(vOffset)
+ , paint(paint)
+ , path(path) {
+ }
+
+ void operator()(size_t start, size_t end) {
+ uint16_t glyphs[1];
+ for (size_t i = start; i < end; i++) {
+ glyphs[0] = layout.getGlyphId(i);
+ float x = hOffset + layout.getX(i);
+ float y = vOffset + layout.getY(i);
+ canvas->drawGlyphsOnPath(glyphs, 1, path, x, y, paint);
+ }
+ }
+private:
+ const Layout& layout;
+ Canvas* canvas;
+ float hOffset;
+ float vOffset;
+ const Paint& paint;
+ const SkPath& path;
+};
+
+void Canvas::drawTextOnPath(const uint16_t* text, int count, int bidiFlags, const SkPath& path,
+ float hOffset, float vOffset, const Paint& paint, Typeface* typeface) {
+ Paint paintCopy(paint);
+ Layout layout;
+ MinikinUtils::doLayout(&layout, &paintCopy, bidiFlags, typeface, text, 0, count, count);
+ hOffset += MinikinUtils::hOffsetForTextAlign(&paintCopy, layout, path);
+
+ // Set align to left for drawing, as we don't want individual
+ // glyphs centered or right-aligned; the offset above takes
+ // care of all alignment.
+ paintCopy.setTextAlign(Paint::kLeft_Align);
+
+ DrawTextOnPathFunctor f(layout, this, hOffset, vOffset, paintCopy, path);
+ MinikinUtils::forFontRun(layout, &paintCopy, f);
+}
+
+} // namespace android
diff --git a/libs/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
similarity index 92%
rename from libs/hwui/Canvas.h
rename to libs/hwui/hwui/Canvas.h
index 27facdf..dc669f0 100644
--- a/libs/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -59,6 +59,9 @@
};
typedef uirenderer::VectorDrawable::Tree VectorDrawableRoot;
+class Paint;
+struct Typeface;
+
class ANDROID_API Canvas {
public:
virtual ~Canvas() {};
@@ -207,12 +210,12 @@
* drawText: count is of glyphs
* totalAdvance: used to define width of text decorations (underlines, strikethroughs).
*/
- virtual void drawText(const uint16_t* glyphs, const float* positions, int count,
+ virtual void drawGlyphs(const uint16_t* glyphs, const float* positions, int count,
const SkPaint& paint, float x, float y,
float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
float totalAdvance) = 0;
/** drawTextOnPath: count is of glyphs */
- virtual void drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
+ virtual void drawGlyphsOnPath(const uint16_t* glyphs, int count, const SkPath& path,
float hOffset, float vOffset, const SkPaint& paint) = 0;
/**
@@ -229,6 +232,16 @@
*/
virtual void drawVectorDrawable(VectorDrawableRoot* tree);
+ /**
+ * Converts utf16 text to glyphs, calculating position and boundary,
+ * and delegating the final draw to virtual drawGlyphs method.
+ */
+ void drawText(const uint16_t* text, int start, int count, int contextCount,
+ float x, float y, int bidiFlags, const Paint& origPaint, Typeface* typeface);
+
+ void drawTextOnPath(const uint16_t* text, int count, int bidiFlags, const SkPath& path,
+ float hOffset, float vOffset, const Paint& paint, Typeface* typeface);
+
protected:
void drawTextDecorations(float x, float y, float length, const SkPaint& paint);
};
diff --git a/core/jni/android/graphics/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp
similarity index 98%
rename from core/jni/android/graphics/MinikinSkia.cpp
rename to libs/hwui/hwui/MinikinSkia.cpp
index 8ac5d46..b9e3358 100644
--- a/core/jni/android/graphics/MinikinSkia.cpp
+++ b/libs/hwui/hwui/MinikinSkia.cpp
@@ -14,14 +14,12 @@
* limitations under the License.
*/
-#include <SkTypeface.h>
-#include <SkPaint.h>
-
-#define LOG_TAG "Minikin"
-#include <cutils/log.h>
-
#include "MinikinSkia.h"
+#include <SkPaint.h>
+#include <SkTypeface.h>
+#include <cutils/log.h>
+
namespace android {
MinikinFontSkia::MinikinFontSkia(SkTypeface *typeface) :
diff --git a/core/jni/android/graphics/MinikinSkia.h b/libs/hwui/hwui/MinikinSkia.h
similarity index 92%
rename from core/jni/android/graphics/MinikinSkia.h
rename to libs/hwui/hwui/MinikinSkia.h
index 8f469ba..1d50168 100644
--- a/core/jni/android/graphics/MinikinSkia.h
+++ b/libs/hwui/hwui/MinikinSkia.h
@@ -17,11 +17,15 @@
#ifndef _ANDROID_GRAPHICS_MINIKIN_SKIA_H_
#define _ANDROID_GRAPHICS_MINIKIN_SKIA_H_
+#include <cutils/compiler.h>
#include <minikin/MinikinFont.h>
+class SkPaint;
+class SkTypeface;
+
namespace android {
-class MinikinFontSkia : public MinikinFont {
+class ANDROID_API MinikinFontSkia : public MinikinFont {
public:
// Note: this takes ownership of the reference (will unref on dtor)
explicit MinikinFontSkia(SkTypeface *typeface);
diff --git a/core/jni/android/graphics/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
similarity index 88%
rename from core/jni/android/graphics/MinikinUtils.cpp
rename to libs/hwui/hwui/MinikinUtils.cpp
index 309d35b..67b775d 100644
--- a/core/jni/android/graphics/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -13,22 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+#include "MinikinUtils.h"
-#define LOG_TAG "Minikin"
+#include "Paint.h"
+#include "SkPathMeasure.h"
+#include "Typeface.h"
+
#include <cutils/log.h>
#include <string>
-#include "SkPathMeasure.h"
-#include "Paint.h"
-#include "TypefaceImpl.h"
-
-#include "MinikinUtils.h"
-
namespace android {
FontStyle MinikinUtils::prepareMinikinPaint(MinikinPaint* minikinPaint, FontCollection** pFont,
- const Paint* paint, TypefaceImpl* typeface) {
- const TypefaceImpl* resolvedFace = TypefaceImpl_resolveDefault(typeface);
+ const Paint* paint, Typeface* typeface) {
+ const Typeface* resolvedFace = Typeface::resolveDefault(typeface);
*pFont = resolvedFace->fFontCollection;
FontStyle resolved = resolvedFace->fStyle;
@@ -53,7 +51,7 @@
}
void MinikinUtils::doLayout(Layout* layout, const Paint* paint, int bidiFlags,
- TypefaceImpl* typeface, const uint16_t* buf, size_t start, size_t count,
+ Typeface* typeface, const uint16_t* buf, size_t start, size_t count,
size_t bufSize) {
FontCollection *font;
MinikinPaint minikinPaint;
@@ -62,7 +60,7 @@
layout->doLayout(buf, start, count, bufSize, bidiFlags, minikinStyle, minikinPaint);
}
-float MinikinUtils::measureText(const Paint* paint, int bidiFlags, TypefaceImpl* typeface,
+float MinikinUtils::measureText(const Paint* paint, int bidiFlags, Typeface* typeface,
const uint16_t* buf, size_t start, size_t count, size_t bufSize, float *advances) {
FontCollection *font;
MinikinPaint minikinPaint;
@@ -71,8 +69,8 @@
font, advances);
}
-bool MinikinUtils::hasVariationSelector(TypefaceImpl* typeface, uint32_t codepoint, uint32_t vs) {
- const TypefaceImpl* resolvedFace = TypefaceImpl_resolveDefault(typeface);
+bool MinikinUtils::hasVariationSelector(Typeface* typeface, uint32_t codepoint, uint32_t vs) {
+ const Typeface* resolvedFace = Typeface::resolveDefault(typeface);
return resolvedFace->fFontCollection->hasVariationSelector(codepoint, vs);
}
diff --git a/core/jni/android/graphics/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h
similarity index 72%
rename from core/jni/android/graphics/MinikinUtils.h
rename to libs/hwui/hwui/MinikinUtils.h
index 9152539..cfaa961 100644
--- a/core/jni/android/graphics/MinikinUtils.h
+++ b/libs/hwui/hwui/MinikinUtils.h
@@ -24,33 +24,34 @@
#ifndef _ANDROID_GRAPHICS_MINIKIN_UTILS_H_
#define _ANDROID_GRAPHICS_MINIKIN_UTILS_H_
+#include <cutils/compiler.h>
#include <minikin/Layout.h>
#include "Paint.h"
#include "MinikinSkia.h"
-#include "TypefaceImpl.h"
+#include "Typeface.h"
namespace android {
class MinikinUtils {
public:
- static FontStyle prepareMinikinPaint(MinikinPaint* minikinPaint, FontCollection** pFont,
- const Paint* paint, TypefaceImpl* typeface);
+ ANDROID_API static FontStyle prepareMinikinPaint(MinikinPaint* minikinPaint, FontCollection** pFont,
+ const Paint* paint, Typeface* typeface);
- static void doLayout(Layout* layout, const Paint* paint, int bidiFlags,
- TypefaceImpl* typeface, const uint16_t* buf, size_t start, size_t count,
+ ANDROID_API static void doLayout(Layout* layout, const Paint* paint, int bidiFlags,
+ Typeface* typeface, const uint16_t* buf, size_t start, size_t count,
size_t bufSize);
- static float measureText(const Paint* paint, int bidiFlags, TypefaceImpl* typeface,
+ ANDROID_API static float measureText(const Paint* paint, int bidiFlags, Typeface* typeface,
const uint16_t* buf, size_t start, size_t count, size_t bufSize, float *advances);
- static bool hasVariationSelector(TypefaceImpl* typeface, uint32_t codepoint, uint32_t vs);
+ ANDROID_API static bool hasVariationSelector(Typeface* typeface, uint32_t codepoint, uint32_t vs);
- static float xOffsetForTextAlign(Paint* paint, const Layout& layout);
+ ANDROID_API static float xOffsetForTextAlign(Paint* paint, const Layout& layout);
- static float hOffsetForTextAlign(Paint* paint, const Layout& layout, const SkPath& path);
+ ANDROID_API static float hOffsetForTextAlign(Paint* paint, const Layout& layout, const SkPath& path);
// f is a functor of type void f(size_t start, size_t end);
template <typename F>
- static void forFontRun(const Layout& layout, Paint* paint, F& f) {
+ ANDROID_API static void forFontRun(const Layout& layout, Paint* paint, F& f) {
float saveSkewX = paint->getTextSkewX();
bool savefakeBold = paint->isFakeBoldText();
MinikinFont* curFont = NULL;
diff --git a/core/jni/android/graphics/Paint.h b/libs/hwui/hwui/Paint.h
similarity index 93%
rename from core/jni/android/graphics/Paint.h
rename to libs/hwui/hwui/Paint.h
index cb6e622..69c321c 100644
--- a/core/jni/android/graphics/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -17,6 +17,8 @@
#ifndef ANDROID_GRAPHICS_PAINT_H_
#define ANDROID_GRAPHICS_PAINT_H_
+#include <cutils/compiler.h>
+
#include <SkPaint.h>
#include <string>
@@ -24,7 +26,7 @@
namespace android {
-class Paint : public SkPaint {
+class ANDROID_API Paint : public SkPaint {
public:
Paint();
Paint(const Paint& paint);
@@ -45,7 +47,7 @@
return mLetterSpacing;
}
- void setFontFeatureSettings(const std::string &fontFeatureSettings) {
+ void setFontFeatureSettings(const std::string& fontFeatureSettings) {
mFontFeatureSettings = fontFeatureSettings;
}
diff --git a/core/jni/android/graphics/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp
similarity index 95%
rename from core/jni/android/graphics/PaintImpl.cpp
rename to libs/hwui/hwui/PaintImpl.cpp
index bd513ae..1172a0e 100644
--- a/core/jni/android/graphics/PaintImpl.cpp
+++ b/libs/hwui/hwui/PaintImpl.cpp
@@ -15,10 +15,6 @@
*/
#include "Paint.h"
-#include <SkPaint.h>
-
-#define LOG_TAG "Paint"
-#include <cutils/log.h>
namespace android {
diff --git a/core/jni/android/graphics/TypefaceImpl.cpp b/libs/hwui/hwui/Typeface.cpp
similarity index 75%
rename from core/jni/android/graphics/TypefaceImpl.cpp
rename to libs/hwui/hwui/Typeface.cpp
index da56290..fa8ad5d 100644
--- a/core/jni/android/graphics/TypefaceImpl.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -20,26 +20,21 @@
* being, that choice is hidden under the USE_MINIKIN compile-time flag.
*/
-#define LOG_TAG "TypefaceImpl"
+#include "Typeface.h"
-#include "jni.h" // for jlong, remove when being passed proper type
-
+#include "MinikinSkia.h"
#include "SkTypeface.h"
+#include "SkPaint.h"
-#include <vector>
#include <minikin/FontCollection.h>
#include <minikin/FontFamily.h>
#include <minikin/Layout.h>
-#include "SkPaint.h"
-#include "MinikinSkia.h"
-
-#include "TypefaceImpl.h"
-#include "Utils.h"
+#include <utils/Log.h>
namespace android {
// Resolve the 1..9 weight based on base weight and bold flag
-static void resolveStyle(TypefaceImpl* typeface) {
+static void resolveStyle(Typeface* typeface) {
int weight = typeface->fBaseWeight / 100;
if (typeface->fSkiaStyle & SkTypeface::kBold) {
weight += 3;
@@ -51,7 +46,7 @@
typeface->fStyle = FontStyle(weight, italic);
}
-TypefaceImpl* gDefaultTypeface = NULL;
+Typeface* gDefaultTypeface = NULL;
pthread_once_t gDefaultTypefaceOnce = PTHREAD_ONCE_INIT;
// This installs a default typeface (from a hardcoded path) that allows
@@ -90,7 +85,7 @@
if (gDefaultTypeface == NULL) {
// We expect the client to set a default typeface, but provide a
// default so we can make progress before that happens.
- gDefaultTypeface = new TypefaceImpl;
+ gDefaultTypeface = new Typeface;
gDefaultTypeface->fFontCollection = makeFontCollection();
gDefaultTypeface->fSkiaStyle = SkTypeface::kNormal;
gDefaultTypeface->fBaseWeight = 400;
@@ -98,7 +93,7 @@
}
}
-TypefaceImpl* TypefaceImpl_resolveDefault(TypefaceImpl* src) {
+Typeface* Typeface::resolveDefault(Typeface* src) {
if (src == NULL) {
pthread_once(&gDefaultTypefaceOnce, getDefaultTypefaceOnce);
return gDefaultTypeface;
@@ -107,9 +102,9 @@
}
}
-TypefaceImpl* TypefaceImpl_createFromTypeface(TypefaceImpl* src, SkTypeface::Style style) {
- TypefaceImpl* resolvedFace = TypefaceImpl_resolveDefault(src);
- TypefaceImpl* result = new TypefaceImpl;
+Typeface* Typeface::createFromTypeface(Typeface* src, SkTypeface::Style style) {
+ Typeface* resolvedFace = Typeface::resolveDefault(src);
+ Typeface* result = new Typeface;
if (result != 0) {
result->fFontCollection = resolvedFace->fFontCollection;
result->fFontCollection->Ref();
@@ -120,9 +115,9 @@
return result;
}
-TypefaceImpl* TypefaceImpl_createWeightAlias(TypefaceImpl* src, int weight) {
- TypefaceImpl* resolvedFace = TypefaceImpl_resolveDefault(src);
- TypefaceImpl* result = new TypefaceImpl;
+Typeface* Typeface::createWeightAlias(Typeface* src, int weight) {
+ Typeface* resolvedFace = Typeface::resolveDefault(src);
+ Typeface* result = new Typeface;
if (result != 0) {
result->fFontCollection = resolvedFace->fFontCollection;
result->fFontCollection->Ref();
@@ -133,15 +128,10 @@
return result;
}
-TypefaceImpl* TypefaceImpl_createFromFamilies(const jlong* families, size_t size) {
- std::vector<FontFamily *>familyVec;
- for (size_t i = 0; i < size; i++) {
- FontFamily* family = reinterpret_cast<FontFamily*>(families[i]);
- familyVec.push_back(family);
- }
- TypefaceImpl* result = new TypefaceImpl;
- result->fFontCollection = new FontCollection(familyVec);
- if (size == 0) {
+Typeface* Typeface::createFromFamilies(const std::vector<FontFamily*>& families) {
+ Typeface* result = new Typeface;
+ result->fFontCollection = new FontCollection(families);
+ if (families.empty()) {
ALOGW("createFromFamilies creating empty collection");
result->fSkiaStyle = SkTypeface::kNormal;
} else {
@@ -162,18 +152,12 @@
return result;
}
-void TypefaceImpl_unref(TypefaceImpl* face) {
- if (face != NULL) {
- face->fFontCollection->Unref();
- }
- delete face;
+void Typeface::unref() {
+ fFontCollection->Unref();
+ delete this;
}
-int TypefaceImpl_getStyle(TypefaceImpl* face) {
- return face->fSkiaStyle;
-}
-
-void TypefaceImpl_setDefault(TypefaceImpl* face) {
+void Typeface::setDefault(Typeface* face) {
gDefaultTypeface = face;
}
diff --git a/libs/hwui/hwui/Typeface.h b/libs/hwui/hwui/Typeface.h
new file mode 100644
index 0000000..8862e5a
--- /dev/null
+++ b/libs/hwui/hwui/Typeface.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef _ANDROID_GRAPHICS_TYPEFACE_IMPL_H_
+#define _ANDROID_GRAPHICS_TYPEFACE_IMPL_H_
+
+#include "SkTypeface.h"
+
+#include <cutils/compiler.h>
+#include <minikin/FontCollection.h>
+#include <vector>
+
+namespace android {
+
+struct ANDROID_API Typeface {
+ FontCollection *fFontCollection;
+
+ // style used for constructing and querying Typeface objects
+ SkTypeface::Style fSkiaStyle;
+ // base weight in CSS-style units, 100..900
+ int fBaseWeight;
+
+ // resolved style actually used for rendering
+ FontStyle fStyle;
+
+ void unref();
+
+ static Typeface* resolveDefault(Typeface* src);
+
+ static Typeface* createFromTypeface(Typeface* src, SkTypeface::Style style);
+
+ static Typeface* createWeightAlias(Typeface* src, int baseweight);
+
+ static Typeface* createFromFamilies(const std::vector<FontFamily*>& families);
+
+ static void setDefault(Typeface* face);
+};
+
+}
+
+#endif // _ANDROID_GRAPHICS_TYPEFACE_IMPL_H_
diff --git a/libs/hwui/hwui_static_deps.mk b/libs/hwui/hwui_static_deps.mk
index 7d4ef0f..2990952 100644
--- a/libs/hwui/hwui_static_deps.mk
+++ b/libs/hwui/hwui_static_deps.mk
@@ -21,8 +21,11 @@
libskia \
libui \
libgui \
- libprotobuf-cpp-lite
+ libprotobuf-cpp-lite \
+ libharfbuzz_ng \
+ libft2 \
+ libminikin
ifneq (false,$(ANDROID_ENABLE_RENDERSCRIPT))
LOCAL_SHARED_LIBRARIES += libRS libRScpp
-endif
\ No newline at end of file
+endif
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index c539d63..31eec8c 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -19,7 +19,6 @@
#include "AnimationContext.h"
#include "Caches.h"
-#include "Canvas.h"
#include "DeferredLayerUpdater.h"
#include "EglManager.h"
#include "LayerUpdateQueue.h"
@@ -27,6 +26,7 @@
#include "OpenGLRenderer.h"
#include "Properties.h"
#include "RenderThread.h"
+#include "hwui/Canvas.h"
#include "renderstate/RenderState.h"
#include "renderstate/Stencil.h"
#include "protos/hwui.pb.h"
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index c809ff4..a4aee61 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -87,7 +87,7 @@
*outTotalAdvance = totalAdvance;
}
-void TestUtils::drawTextToCanvas(TestCanvas* canvas, const char* text,
+void TestUtils::drawUtf8ToCanvas(TestCanvas* canvas, const char* text,
const SkPaint& paint, float x, float y) {
// drawing text requires GlyphID TextEncoding (which JNI layer would have done)
LOG_ALWAYS_FATAL_IF(paint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding,
@@ -113,11 +113,11 @@
// Force left alignment, since alignment offset is already baked in
SkPaint alignPaintCopy(paint);
alignPaintCopy.setTextAlign(SkPaint::kLeft_Align);
- canvas->drawText(glyphs.data(), positions.data(), glyphs.size(), alignPaintCopy, x, y,
+ canvas->drawGlyphs(glyphs.data(), positions.data(), glyphs.size(), alignPaintCopy, x, y,
bounds.left, bounds.top, bounds.right, bounds.bottom, totalAdvance);
}
-void TestUtils::drawTextToCanvas(TestCanvas* canvas, const char* text,
+void TestUtils::drawUtf8ToCanvas(TestCanvas* canvas, const char* text,
const SkPaint& paint, const SkPath& path) {
// drawing text requires GlyphID TextEncoding (which JNI layer would have done)
LOG_ALWAYS_FATAL_IF(paint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding,
@@ -130,7 +130,7 @@
SkUnichar unichar = SkUTF8_NextUnichar(&text);
glyphs.push_back(autoCache.getCache()->unicharToGlyph(unichar));
}
- canvas->drawTextOnPath(glyphs.data(), glyphs.size(), path, 0, 0, paint);
+ canvas->drawGlyphsOnPath(glyphs.data(), glyphs.size(), path, 0, 0, paint);
}
void TestUtils::TestTask::run() {
@@ -143,5 +143,13 @@
renderState.onGLContextDestroyed();
}
+std::unique_ptr<uint16_t[]> TestUtils::utf8ToUtf16(const char* str) {
+ const size_t strLen = strlen(str);
+ const ssize_t utf16Len = utf8_to_utf16_length((uint8_t*) str, strLen);
+ std::unique_ptr<uint16_t[]> dst(new uint16_t[utf16Len + 1]);
+ utf8_to_utf16((uint8_t*) str, strLen, (char16_t*) dst.get());
+ return dst;
+}
+
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 28ac116..a5e7a5f 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -207,12 +207,14 @@
std::vector<glyph_t>* outGlyphs, std::vector<float>* outPositions,
float* outTotalAdvance, Rect* outBounds);
- static void drawTextToCanvas(TestCanvas* canvas, const char* text,
+ static void drawUtf8ToCanvas(TestCanvas* canvas, const char* text,
const SkPaint& paint, float x, float y);
- static void drawTextToCanvas(TestCanvas* canvas, const char* text,
+ static void drawUtf8ToCanvas(TestCanvas* canvas, const char* text,
const SkPaint& paint, const SkPath& path);
+ static std::unique_ptr<uint16_t[]> utf8ToUtf16(const char* str);
+
private:
static void syncHierarchyPropertiesAndDisplayListImpl(RenderNode* node) {
node->syncProperties();
diff --git a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
index 43e247e..ab368c05 100644
--- a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
@@ -136,9 +136,9 @@
textPaint.setAntiAlias(true);
char buf[256];
snprintf(buf, sizeof(buf), "This card is #%d", cardId);
- TestUtils::drawTextToCanvas(&canvas, buf, textPaint, cardHeight, dp(25));
+ TestUtils::drawUtf8ToCanvas(&canvas, buf, textPaint, cardHeight, dp(25));
textPaint.setTextSize(dp(15));
- TestUtils::drawTextToCanvas(&canvas, "This is some more text on the card", textPaint,
+ TestUtils::drawUtf8ToCanvas(&canvas, "This is some more text on the card", textPaint,
cardHeight, dp(45));
canvas.drawBitmap(createRandomCharIcon(), dp(10), dp(10), nullptr);
diff --git a/libs/hwui/tests/common/scenes/TextAnimation.cpp b/libs/hwui/tests/common/scenes/TextAnimation.cpp
index 1823db2..be8f48b9 100644
--- a/libs/hwui/tests/common/scenes/TextAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/TextAnimation.cpp
@@ -39,14 +39,14 @@
paint.setColor(Color::Black);
for (int i = 0; i < 10; i++) {
- TestUtils::drawTextToCanvas(&canvas, "Test string", paint, 400, i * 100);
+ TestUtils::drawUtf8ToCanvas(&canvas, "Test string", paint, 400, i * 100);
}
SkPath path;
path.addOval(SkRect::MakeLTRB(100, 100, 300, 300));
paint.setColor(Color::Blue_500);
- TestUtils::drawTextToCanvas(&canvas, "This is a neat circle of text!", paint, path);
+ TestUtils::drawUtf8ToCanvas(&canvas, "This is a neat circle of text!", paint, path);
});
canvas.drawRenderNode(card.get());
}
diff --git a/libs/hwui/tests/unit/CanvasStateTests.cpp b/libs/hwui/tests/unit/CanvasStateTests.cpp
index 68d74ee..0afabd8 100644
--- a/libs/hwui/tests/unit/CanvasStateTests.cpp
+++ b/libs/hwui/tests/unit/CanvasStateTests.cpp
@@ -16,9 +16,9 @@
#include "CanvasState.h"
-#include "Canvas.h"
#include "Matrix.h"
#include "Rect.h"
+#include "hwui/Canvas.h"
#include "utils/LinearAllocator.h"
#include <gtest/gtest.h>
diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp
index 31555f2..a467b5c 100644
--- a/libs/hwui/tests/unit/FrameBuilderTests.cpp
+++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp
@@ -274,8 +274,8 @@
paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
paint.setAntiAlias(true);
paint.setTextSize(50);
- TestUtils::drawTextToCanvas(&canvas, "Test string1", paint, 100, 0); // will be top clipped
- TestUtils::drawTextToCanvas(&canvas, "Test string1", paint, 100, 100); // not clipped
+ TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 0); // will be top clipped
+ TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 100); // not clipped
});
FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(400, 400), 400, 400,
TestUtils::createSyncedNodeList(node), sLightGeometry, nullptr);
@@ -305,7 +305,7 @@
textPaint.setStrikeThruText(true);
textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
for (int i = 0; i < LOOPS; i++) {
- TestUtils::drawTextToCanvas(&canvas, "test text", textPaint, 10, 100 * (i + 1));
+ TestUtils::drawUtf8ToCanvas(&canvas, "test text", textPaint, 10, 100 * (i + 1));
}
});
FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 2000), 200, 2000,
@@ -361,7 +361,7 @@
// They'll get merged, but with
for (auto style : styles) {
paint.setStyle(style);
- TestUtils::drawTextToCanvas(&canvas, "Test string1", paint, 100, 100);
+ TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 100);
}
});
FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(400, 400), 400, 400,
diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
index 5e613fd..6ab5110 100644
--- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp
+++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
@@ -18,6 +18,8 @@
#include <RecordedOp.h>
#include <RecordingCanvas.h>
+#include <hwui/Paint.h>
+#include <minikin/Layout.h>
#include <tests/common/TestUtils.h>
#include <utils/Color.h>
@@ -131,13 +133,13 @@
<< "Non-rounded rects should be converted";
}
-TEST(RecordingCanvas, drawText) {
+TEST(RecordingCanvas, drawGlyphs) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setTextSize(20);
paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
- TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
+ TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
});
int count = 0;
@@ -152,7 +154,7 @@
ASSERT_EQ(1, count);
}
-TEST(RecordingCanvas, drawText_strikeThruAndUnderline) {
+TEST(RecordingCanvas, drawGlyphs_strikeThruAndUnderline) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
SkPaint paint;
paint.setAntiAlias(true);
@@ -162,7 +164,7 @@
for (int j = 0; j < 2; j++) {
paint.setUnderlineText(i != 0);
paint.setStrikeThruText(j != 0);
- TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
+ TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
}
}
});
@@ -184,18 +186,18 @@
EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough
}
-TEST(RecordingCanvas, drawText_forceAlignLeft) {
+TEST(RecordingCanvas, drawGlyphs_forceAlignLeft) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setTextSize(20);
paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
paint.setTextAlign(SkPaint::kLeft_Align);
- TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
+ TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
paint.setTextAlign(SkPaint::kCenter_Align);
- TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
+ TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
paint.setTextAlign(SkPaint::kRight_Align);
- TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
+ TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
});
int count = 0;
@@ -223,8 +225,7 @@
auto op = *(dl->getOps()[0]);
EXPECT_EQ(RecordedOpId::RectOp, op.opId);
EXPECT_EQ(nullptr, op.localClip);
- EXPECT_TRUE(op.unmappedBounds.contains(Rect(-1000, -1000, 1000, 1000)))
- << "no clip, unmappedBounds should resolve to be much larger than DL bounds";
+ EXPECT_TRUE(op.unmappedBounds.contains(Rect(200, 200))) << "Expect recording/clip bounds";
}
TEST(RecordingCanvas, backgroundAndImage) {
@@ -577,7 +578,7 @@
canvas.drawRect(0, 0, 200, 10, paint);
SkPaint paintCopy(paint);
canvas.drawRect(0, 10, 200, 20, paintCopy);
- TestUtils::drawTextToCanvas(&canvas, "helloworld", paint, 50, 25);
+ TestUtils::drawUtf8ToCanvas(&canvas, "helloworld", paint, 50, 25);
// only here do we use different paint ptr
paint.setColor(SK_ColorRED);
@@ -598,5 +599,54 @@
EXPECT_NE(&paint, ops[3]->paint);
}
+TEST(RecordingCanvas, drawText) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ Paint paint;
+ paint.setAntiAlias(true);
+ paint.setTextSize(20);
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ std::unique_ptr<uint16_t[]> dst = TestUtils::utf8ToUtf16("HELLO");
+ canvas.drawText(dst.get(), 0, 5, 5, 25, 25, kBidi_Force_LTR, paint, NULL);
+ });
+
+ int count = 0;
+ playbackOps(*dl, [&count](const RecordedOp& op) {
+ count++;
+ ASSERT_EQ(RecordedOpId::TextOp, op.opId);
+ EXPECT_EQ(nullptr, op.localClip);
+ EXPECT_TRUE(op.localMatrix.isIdentity());
+ EXPECT_TRUE(op.unmappedBounds.getHeight() >= 10);
+ EXPECT_TRUE(op.unmappedBounds.getWidth() >= 25);
+ });
+ ASSERT_EQ(1, count);
+}
+
+TEST(RecordingCanvas, drawTextInHighContrast) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ canvas.setHighContrastText(true);
+ Paint paint;
+ paint.setColor(SK_ColorWHITE);
+ paint.setAntiAlias(true);
+ paint.setTextSize(20);
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ std::unique_ptr<uint16_t[]> dst = TestUtils::utf8ToUtf16("HELLO");
+ canvas.drawText(dst.get(), 0, 5, 5, 25, 25, kBidi_Force_LTR, paint, NULL);
+ });
+
+ int count = 0;
+ playbackOps(*dl, [&count](const RecordedOp& op) {
+ ASSERT_EQ(RecordedOpId::TextOp, op.opId);
+ if (count++ == 0) {
+ EXPECT_EQ(SK_ColorBLACK, op.paint->getColor());
+ EXPECT_EQ(SkPaint::kStrokeAndFill_Style, op.paint->getStyle());
+ } else {
+ EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
+ EXPECT_EQ(SkPaint::kFill_Style, op.paint->getStyle());
+ }
+
+ });
+ ASSERT_EQ(2, count);
+}
+
} // namespace uirenderer
} // namespace android
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index abb6f4e..a4484e7 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -256,6 +256,11 @@
public static final int ENCODING_AAC_HE_V2 = 12;
/** Audio data format: compressed audio wrapped in PCM for HDMI
* or S/PDIF passthrough.
+ * IEC61937 uses a stereo stream of 16-bit samples as the wrapper.
+ * So the channel mask for the track must be {@link #CHANNEL_OUT_STEREO}.
+ * Data should be written to the stream in a short[] array.
+ * If the data is written in a byte[] array then there may be endian problems
+ * on some platforms when converting to short internally.
*/
public static final int ENCODING_IEC61937 = 13;
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index e1dab09..d9caf03 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -806,6 +806,15 @@
}
mSampleRate = sampleRateInHz;
+ // IEC61937 is based on stereo. We could coerce it to stereo.
+ // But the application needs to know the stream is stereo so that
+ // it is encoded and played correctly. So better to just reject it.
+ if (audioFormat == AudioFormat.ENCODING_IEC61937
+ && channelConfig != AudioFormat.CHANNEL_OUT_STEREO) {
+ throw new IllegalArgumentException(
+ "ENCODING_IEC61937 must be configured as CHANNEL_OUT_STEREO");
+ }
+
//--------------
// channel config
mChannelConfiguration = channelConfig;
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index ed358d3..dbedf34 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -830,7 +830,9 @@
} catch (IOException e) {
// Ignore exceptions in order to keep the compatibility with the old versions of
// ExifInterface.
- Log.w(TAG, "Invalid JPEG", e);
+ Log.w(TAG, "Invalid JPEG: ExifInterface got an unsupported image format file"
+ + "(ExifInterface supports JPEG and some RAW image formats only) "
+ + "or a corrupted JPEG file to ExifInterface.", e);
}
if (DEBUG) {
@@ -1189,6 +1191,10 @@
++bytesRead;
while (true) {
marker = dataInputStream.readByte();
+ if (marker == -1) {
+ Log.w(TAG, "Reading JPEG has ended unexpectedly");
+ break;
+ }
if (marker != MARKER) {
throw new IOException("Invalid marker:" + Integer.toHexString(marker & 0xff));
}
@@ -1207,7 +1213,8 @@
int length = dataInputStream.readUnsignedShort() - 2;
bytesRead += 2;
if (DEBUG) {
- Log.d(TAG, "JPEG segment: " + marker + " (length: " + (length + 2) + ")");
+ Log.d(TAG, "JPEG segment: " + Integer.toHexString(marker & 0xff) + " (length: "
+ + (length + 2) + ")");
}
if (length < 0) {
throw new IOException("Invalid length");
@@ -1270,7 +1277,9 @@
case MARKER_SOF13:
case MARKER_SOF14:
case MARKER_SOF15: {
- dataInputStream.skipBytes(1);
+ if (dataInputStream.skipBytes(1) != 1) {
+ throw new IOException("Invalid SOFx");
+ }
setAttribute("ImageLength",
String.valueOf(dataInputStream.readUnsignedShort()));
setAttribute("ImageWidth", String.valueOf(dataInputStream.readUnsignedShort()));
@@ -1285,7 +1294,9 @@
if (length < 0) {
throw new IOException("Invalid length");
}
- dataInputStream.skipBytes(length);
+ if (dataInputStream.skipBytes(length) != length) {
+ throw new IOException("Invalid JPEG segment");
+ }
bytesRead += length;
}
}
@@ -1317,10 +1328,15 @@
byte[] bytes = new byte[4096];
while (true) {
- if (dataInputStream.readByte() != MARKER) {
+ byte marker = dataInputStream.readByte();
+ if (marker == -1) {
+ Log.w(TAG, "Reading JPEG has ended unexpectedly");
+ break;
+ }
+ if (marker != MARKER) {
throw new IOException("Invalid marker");
}
- byte marker = dataInputStream.readByte();
+ marker = dataInputStream.readByte();
switch (marker) {
case MARKER_APP1: {
int length = dataInputStream.readUnsignedShort() - 2;
@@ -1644,7 +1660,7 @@
String tagName = (String) sExifTagMapsForReading[hint].get(tagNumber);
if (DEBUG) {
- Log.d(TAG, String.format("hint: %d, tagNumber: %d, tagName: %s, dataFormat: %d," +
+ Log.d(TAG, String.format("hint: %d, tagNumber: %d, tagName: %s, dataFormat: %d, " +
"numberOfComponents: %d", hint, tagNumber, tagName, dataFormat,
numberOfComponents));
}
@@ -2025,6 +2041,12 @@
int bytesWritten = 0;
int dataFormat = getDataFormatOfExifEntryValue(entryValue);
+ if (dataFormat == IFD_FORMAT_STRING) {
+ byte[] asciiArray = (entryValue + '\0').getBytes(Charset.forName("US-ASCII"));
+ dataOutputStream.write(asciiArray);
+ return asciiArray.length;
+ }
+
// Values can be composed of several components. Each component is separated by char ','.
String[] components = entryValue.split(",");
for (String component : components) {
@@ -2037,11 +2059,6 @@
dataOutputStream.writeDouble(Double.parseDouble(component));
bytesWritten += 8;
break;
- case IFD_FORMAT_STRING:
- byte[] asciiArray = (component + '\0').getBytes(Charset.forName("US-ASCII"));
- dataOutputStream.write(asciiArray);
- bytesWritten += asciiArray.length;
- break;
case IFD_FORMAT_SRATIONAL:
String[] rationalNumber = component.split("/");
dataOutputStream.writeInt(Integer.parseInt(rationalNumber[0]));
@@ -2060,11 +2077,31 @@
// See TIFF 6.0 spec Types. page 15.
// Take the first component if there are more than one component.
if (entryValue.contains(",")) {
- entryValue = entryValue.split(",")[0];
+ String[] entryValues = entryValue.split(",");
+ int dataFormat = getDataFormatOfExifEntryValue(entryValues[0]);
+ if (dataFormat == IFD_FORMAT_STRING) {
+ return IFD_FORMAT_STRING;
+ }
+ for (int i = 1; i < entryValues.length; ++i) {
+ if (getDataFormatOfExifEntryValue(entryValues[i]) != dataFormat) {
+ return IFD_FORMAT_STRING;
+ }
+ }
+ return dataFormat;
}
if (entryValue.contains("/")) {
- return IFD_FORMAT_SRATIONAL;
+ String[] rationalNumber = entryValue.split("/");
+ if (rationalNumber.length == 2) {
+ try {
+ Integer.parseInt(rationalNumber[0]);
+ Integer.parseInt(rationalNumber[1]);
+ return IFD_FORMAT_SRATIONAL;
+ } catch (NumberFormatException e) {
+ // Ignored
+ }
+ }
+ return IFD_FORMAT_STRING;
}
try {
Integer.parseInt(entryValue);
@@ -2084,6 +2121,9 @@
// Determines the size of EXIF entry value.
private static int getSizeOfExifEntryValue(int dataFormat, String entryValue) {
// See TIFF 6.0 spec Types page 15.
+ if (dataFormat == IFD_FORMAT_STRING) {
+ return (entryValue + '\0').getBytes(Charset.forName("US-ASCII")).length;
+ }
int bytesEstimated = 0;
String[] components = entryValue.split(",");
for (String component : components) {
@@ -2094,10 +2134,6 @@
case IFD_FORMAT_DOUBLE:
bytesEstimated += 8;
break;
- case IFD_FORMAT_STRING:
- bytesEstimated
- += (component + '\0').getBytes(Charset.forName("US-ASCII")).length;
- break;
case IFD_FORMAT_SRATIONAL:
bytesEstimated += 8;
break;
diff --git a/media/java/android/media/MediaMetadata.java b/media/java/android/media/MediaMetadata.java
index 722605f..9ebf10f 100644
--- a/media/java/android/media/MediaMetadata.java
+++ b/media/java/android/media/MediaMetadata.java
@@ -365,7 +365,7 @@
}
private MediaMetadata(Parcel in) {
- mBundle = in.readBundle();
+ mBundle = Bundle.setDefusable(in.readBundle(), true);
}
/**
diff --git a/packages/DocumentsUI/Android.mk b/packages/DocumentsUI/Android.mk
index d5e48b5..3197abd 100644
--- a/packages/DocumentsUI/Android.mk
+++ b/packages/DocumentsUI/Android.mk
@@ -31,9 +31,13 @@
--extra-packages android.support.v7.recyclerview
LOCAL_JACK_FLAGS := \
- -D jack.assert.policy=enable \
-D jack.optimization.inner-class.accessors=true
+# Only enable asserts on userdebug/eng builds
+ifneq (,$(filter userdebug eng, $(TARGET_BUILD_VARIANT)))
+LOCAL_JACK_FLAGS += -D jack.assert.policy=enable
+endif
+
LOCAL_PACKAGE_NAME := DocumentsUI
LOCAL_CERTIFICATE := platform
diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml
index a4acf7e..be08385 100644
--- a/packages/DocumentsUI/AndroidManifest.xml
+++ b/packages/DocumentsUI/AndroidManifest.xml
@@ -45,7 +45,7 @@
android:name=".LauncherActivity"
android:theme="@android:style/Theme.NoDisplay"
android:icon="@drawable/ic_files_app"
- android:label="@string/files_label">
+ android:label="@string/downloads_label">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@@ -56,7 +56,7 @@
android:name=".FilesActivity"
android:theme="@style/DocumentsTheme"
android:icon="@drawable/ic_files_app"
- android:label="@string/files_label"
+ android:label="@string/downloads_label"
android:documentLaunchMode="intoExisting">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_launcher_download.png b/packages/DocumentsUI/res/drawable-hdpi/ic_launcher_download.png
new file mode 100644
index 0000000..f958bbd
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-hdpi/ic_launcher_download.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_launcher_download.png b/packages/DocumentsUI/res/drawable-mdpi/ic_launcher_download.png
new file mode 100644
index 0000000..f2e9376
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-mdpi/ic_launcher_download.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_launcher_download.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_launcher_download.png
new file mode 100644
index 0000000..4dc5336
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-xhdpi/ic_launcher_download.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_launcher_download.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_launcher_download.png
new file mode 100644
index 0000000..8716290
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-xxhdpi/ic_launcher_download.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_launcher_download.png b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_launcher_download.png
new file mode 100644
index 0000000..f5be219
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_launcher_download.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable/ic_files_app.xml b/packages/DocumentsUI/res/drawable/ic_files_app.xml
index ff7189e..76e3ba6 100644
--- a/packages/DocumentsUI/res/drawable/ic_files_app.xml
+++ b/packages/DocumentsUI/res/drawable/ic_files_app.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
- android:src="@drawable/icon256"
+ android:src="@drawable/ic_launcher_download"
android:tint="?android:attr/colorControlNormal"
android:autoMirrored="true" />
diff --git a/packages/DocumentsUI/res/drawable/icon256.png b/packages/DocumentsUI/res/drawable/icon256.png
deleted file mode 100644
index 631c951..0000000
--- a/packages/DocumentsUI/res/drawable/icon256.png
+++ /dev/null
Binary files differ
diff --git a/packages/DocumentsUI/res/values/config.xml b/packages/DocumentsUI/res/values/config.xml
index ebb3969..408603e 100644
--- a/packages/DocumentsUI/res/values/config.xml
+++ b/packages/DocumentsUI/res/values/config.xml
@@ -24,5 +24,6 @@
<!-- Indicates if the home directory should be hidden in the roots list, that is presented
in the drawer/left side panel ) -->
<bool name="home_root_hidden">true</bool>
-
+ <!-- Indicates if the advanced roots like internal storage should be hidden in the roots list) -->
+ <bool name="advanced_roots_hidden">true</bool>
</resources>
diff --git a/packages/DocumentsUI/res/values/styles.xml b/packages/DocumentsUI/res/values/styles.xml
index a548d89..b16554c 100644
--- a/packages/DocumentsUI/res/values/styles.xml
+++ b/packages/DocumentsUI/res/values/styles.xml
@@ -36,6 +36,8 @@
<item name="android:windowActionBar">false</item>
<item name="android:windowActionModeOverlay">true</item>
<item name="android:windowNoTitle">true</item>
+ <item name="android:windowTranslucentStatus">true</item>
+ <item name="android:fitsSystemWindows">false</item>
<item name="android:windowSoftInputMode">stateUnspecified|adjustUnspecified</item>
</style>
@@ -43,7 +45,7 @@
<style name="TrimmedHorizontalProgressBar" parent="android:Widget.Material.ProgressBar.Horizontal">
<item name="android:indeterminateDrawable">@drawable/progress_indeterminate_horizontal_material_trimmed</item>
<item name="android:minHeight">3dp</item>
- <item name="android:maxHeight">3dp</item>
+ <item name="android:maxHeight">3dp</item>
</style>
</resources>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index fe61094..3b300d0 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -127,6 +127,7 @@
mSearchManager = new SearchViewManager(this, icicle);
DocumentsToolbar toolbar = (DocumentsToolbar) findViewById(R.id.toolbar);
+ Display.adjustToolbar(toolbar, this);
setActionBar(toolbar);
mNavigator = new NavigationView(
mDrawer,
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Display.java b/packages/DocumentsUI/src/com/android/documentsui/Display.java
index bae2d58..d46a3ea 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Display.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Display.java
@@ -20,13 +20,15 @@
import android.content.Context;
import android.graphics.Point;
import android.util.TypedValue;
+import android.view.WindowManager;
+import android.widget.Toolbar;
/*
* Convenience class for getting display related attributes
*/
public final class Display {
/*
- * Returns the screen width in pixels.
+ * Returns the screen width in raw pixels.
*/
public static float screenWidth(Activity activity) {
Point size = new Point();
@@ -42,15 +44,44 @@
}
/*
- * Returns action bar height in pixels.
+ * Returns action bar height in raw pixels.
*/
public static float actionBarHeight(Context context) {
- int actionBarHeight = 0;
+ int height = 0;
TypedValue tv = new TypedValue();
if (context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) {
- actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data,
+ height = TypedValue.complexToDimensionPixelSize(tv.data,
context.getResources().getDisplayMetrics());
}
- return actionBarHeight;
+ return height;
+ }
+
+ /*
+ * Returns status bar height in raw pixels.
+ */
+ private static int statusBarHeight(Context context) {
+ int height = 0;
+ int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen",
+ "android");
+ if (resourceId > 0) {
+ height = context.getResources().getDimensionPixelSize(resourceId);
+ }
+ return height;
+ }
+
+ /*
+ * Adjusts toolbar for the layout with translucent status bar. Increases the
+ * height of the toolbar and adds padding at the top to accommodate status bar visible above
+ * toolbar.
+ */
+ public static void adjustToolbar(Toolbar toolbar, Activity activity) {
+ if ((activity.getWindow().getAttributes().flags
+ & WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) != 0) {
+ int statusBarHeight = Display.statusBarHeight(activity);
+ toolbar.getLayoutParams().height = (int) (Display.actionBarHeight(activity)
+ + statusBarHeight);
+ toolbar.setPadding(toolbar.getPaddingLeft(), statusBarHeight, toolbar.getPaddingRight(),
+ toolbar.getPaddingBottom());
+ }
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index ba593dc..805d877 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -95,7 +95,7 @@
}
if (mState.restored) {
- if (DEBUG) Log.d(TAG, "Stack already resolved");
+ refreshCurrentRootAndDirectory(AnimationView.ANIM_NONE);
} else {
// We set the activity title in AsyncTask.onPostExecute().
// To prevent talkback from reading aloud the default title, we clear it here.
@@ -108,7 +108,9 @@
// we restore the stack as last used from that app.
if (mState.action == ACTION_PICK_COPY_DESTINATION) {
if (DEBUG) Log.d(TAG, "Launching directly into Home directory.");
- loadRoot(DocumentsContract.buildHomeUri());
+ Uri homeUri = DocumentsContract.buildHomeUri();
+ new LoadRootTask(this, homeUri).executeOnExecutor(
+ ProviderExecutor.forAuthority(homeUri.getAuthority()));
} else {
if (DEBUG) Log.d(TAG, "Attempting to load last used stack for calling package.");
new LoadLastUsedStackTask(this).execute();
@@ -154,6 +156,30 @@
}
}
+ private void onStackRestored(boolean restored, boolean external) {
+ // Show drawer when no stack restored, but only when requesting
+ // non-visual content. However, if we last used an external app,
+ // drawer is always shown.
+
+ boolean showDrawer = false;
+ if (!restored) {
+ showDrawer = true;
+ }
+ if (MimePredicate.mimeMatches(MimePredicate.VISUAL_MIMES, mState.acceptMimes)) {
+ showDrawer = false;
+ }
+ if (external && mState.action == ACTION_GET_CONTENT) {
+ showDrawer = true;
+ }
+ if (mState.action == ACTION_PICK_COPY_DESTINATION) {
+ showDrawer = true;
+ }
+
+ if (showDrawer) {
+ mNavigator.revealRootsDrawer(true);
+ }
+ }
+
public void onAppPicked(ResolveInfo info) {
final Intent intent = new Intent(getIntent());
intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_FORWARD_RESULT);
@@ -164,7 +190,7 @@
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- Log.d(TAG, "onActivityResult() code=" + resultCode);
+ if (DEBUG) Log.d(TAG, "onActivityResult() code=" + resultCode);
// Only relay back results when not canceled; otherwise stick around to
// let the user pick another app/backend.
@@ -203,8 +229,8 @@
mState.action == ACTION_PICK_COPY_DESTINATION) {
title = getResources().getString(R.string.title_save);
} else {
- // If all else fails, just call it "Files".
- title = getResources().getString(R.string.files_label);
+ // If all else fails, just call it "Downloads".
+ title = getResources().getString(R.string.downloads_label);
}
}
@@ -388,7 +414,7 @@
@Override
void onTaskFinished(Uri... uris) {
- Log.d(TAG, "onFinished() " + Arrays.toString(uris));
+ if (DEBUG) Log.d(TAG, "onFinished() " + Arrays.toString(uris));
final Intent intent = new Intent();
if (uris.length == 1) {
@@ -491,8 +517,8 @@
@Override
protected void finish(Void result) {
mState.restored = true;
- mState.external = mExternal;
mOwner.refreshCurrentRootAndDirectory(AnimationView.ANIM_NONE);
+ mOwner.onStackRestored(mRestoredStack, mExternal);
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java b/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java
index 020f2c0..2dbb730 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java
@@ -55,7 +55,7 @@
View drawer = activity.findViewById(R.id.drawer_roots);
Toolbar toolbar = (Toolbar) activity.findViewById(R.id.roots_toolbar);
-
+ Display.adjustToolbar(toolbar, activity);
drawer.getLayoutParams().width = calculateDrawerWidth(activity);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
index 99f306a..a6eba41 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
@@ -182,7 +182,7 @@
@Override
public String getDrawerTitle() {
- return getResources().getString(R.string.files_label);
+ return getResources().getString(R.string.downloads_label);
}
@Override
@@ -414,7 +414,7 @@
@Override
void onTaskFinished(Uri... uris) {
- Log.d(TAG, "onFinished() " + Arrays.toString(uris));
+ if (DEBUG) Log.d(TAG, "onFinished() " + Arrays.toString(uris));
final Intent intent = new Intent();
if (uris.length == 1) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java b/packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java
index 2b6f396..ab45af1 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java
@@ -282,7 +282,7 @@
logInvalidScopedAccessRequest(context, SCOPED_DIRECTORY_ACCESS_ERROR);
return null;
}
- Log.d(TAG, "doc id for " + file + ": " + docId);
+ if (DEBUG) Log.d(TAG, "doc id for " + file + ": " + docId);
final Uri uri = DocumentsContract.buildTreeDocumentUri(EXTERNAL_STORAGE_AUTH, docId);
if (uri == null) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java
index e1b1c09..6ef9154 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java
@@ -16,6 +16,7 @@
package com.android.documentsui;
+import static com.android.documentsui.Shared.DEBUG;
import static com.android.documentsui.model.DocumentInfo.getCursorString;
import android.content.ContentProvider;
@@ -338,7 +339,7 @@
if (predicate.apply(authority)) {
db.delete(TABLE_STATE, StateColumns.AUTHORITY + "=?", new String[] {
authority });
- Log.d(TAG, "Purged state for " + authority);
+ if (DEBUG) Log.d(TAG, "Purged state for " + authority);
}
}
} finally {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
index 54e6287..1864431e 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
@@ -320,6 +320,8 @@
if (root.isHome() && isHomeRootHidden(context)) {
continue;
+ } else if (root.isAdvanced() && areAdvancedRootsHidden(context)) {
+ continue;
} else if (root.isLibrary()) {
if (DEBUG) Log.d(TAG, "Adding " + root + " as library.");
libraries.add(item);
@@ -377,6 +379,13 @@
return context.getResources().getBoolean(R.bool.home_root_hidden);
}
+ /*
+ * Indicates if the advanced roots should be hidden.
+ */
+ private boolean areAdvancedRootsHidden(Context context) {
+ return context.getResources().getBoolean(R.bool.advanced_roots_hidden);
+ }
+
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final Item item = getItem(position);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/SearchViewManager.java b/packages/DocumentsUI/src/com/android/documentsui/SearchViewManager.java
index 63dc2ee..4d0ba4b 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/SearchViewManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/SearchViewManager.java
@@ -16,6 +16,8 @@
package com.android.documentsui;
+import static com.android.documentsui.Shared.DEBUG;
+
import android.annotation.Nullable;
import android.os.Bundle;
import android.provider.DocumentsContract.Root;
@@ -80,7 +82,7 @@
*/
void update(RootInfo root) {
if (mMenu == null) {
- Log.d(TAG, "update called before Search MenuItem installed.");
+ if (DEBUG) Log.d(TAG, "update called before Search MenuItem installed.");
return;
}
@@ -108,7 +110,7 @@
void showMenu(boolean visible) {
if (mMenu == null) {
- Log.d(TAG, "showMenu called before Search MenuItem installed.");
+ if (DEBUG) Log.d(TAG, "showMenu called before Search MenuItem installed.");
return;
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Shared.java b/packages/DocumentsUI/src/com/android/documentsui/Shared.java
index 6f1863e..11c5933 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Shared.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Shared.java
@@ -33,7 +33,7 @@
public static final String TAG = "Documents";
- public static final boolean DEBUG = true;
+ public static final boolean DEBUG = false;
/** Intent action name to pick a copy destination. */
public static final String ACTION_PICK_COPY_DESTINATION =
diff --git a/packages/DocumentsUI/src/com/android/documentsui/State.java b/packages/DocumentsUI/src/com/android/documentsui/State.java
index 16b7660..43468e3 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/State.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/State.java
@@ -85,10 +85,6 @@
public boolean showSize;
public boolean localOnly;
public boolean restored;
- /*
- * Indicates handler was an external app, like photos.
- */
- public boolean external;
// Indicates that a copy operation (or move) includes a directory.
// Why? Directory creation isn't supported by some roots (like Downloads).
@@ -186,7 +182,6 @@
out.writeInt(showSize ? 1 : 0);
out.writeInt(localOnly ? 1 : 0);
out.writeInt(restored ? 1 : 0);
- out.writeInt(external ? 1 : 0);
DurableUtils.writeToParcel(out, stack);
out.writeMap(dirState);
out.writeParcelable(selectedDocuments, 0);
@@ -215,7 +210,6 @@
state.showSize = in.readInt() != 0;
state.localOnly = in.readInt() != 0;
state.restored = in.readInt() != 0;
- state.external = in.readInt() != 0;
DurableUtils.readFromParcel(in, state.stack);
in.readMap(state.dirState, loader);
state.selectedDocuments = in.readParcelable(loader);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 60e4b9a..0810d194 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -506,8 +506,10 @@
getActivity().getWindow().setStatusBarColor(color.data);
if (mActionMode != null) {
- mActionMode.setTitle(Shared.getQuantityString(getActivity(),
- R.plurals.elements_selected, mSelected.size()));
+ final String title = Shared.getQuantityString(getActivity(),
+ R.plurals.elements_selected, mSelected.size());
+ mActionMode.setTitle(title);
+ mRecView.announceForAccessibility(title);
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
index ea1deb4..06cb9aa 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
@@ -157,27 +157,8 @@
@Override
void onModelLoaded(Model model, @ResultType int resultType, boolean isSearch) {
- boolean showDrawer = false;
-
- if (mState.restored) {
- showDrawer = true;
- }
- if (MimePredicate.mimeMatches(MimePredicate.VISUAL_MIMES, mState.acceptMimes)) {
- showDrawer = false;
- }
- if (mState.external && mState.action == ACTION_GET_CONTENT) {
- showDrawer = true;
- }
- if (mState.action == ACTION_PICK_COPY_DESTINATION) {
- showDrawer = true;
- }
-
// When launched into empty root, open drawer.
- if (model.isEmpty()) {
- showDrawer = true;
- }
-
- if (showDrawer && !mState.hasInitialLocationChanged() && !isSearch) {
+ if (model.isEmpty() && !mState.hasInitialLocationChanged() && !isSearch) {
// This noops on layouts without drawer, so no need to guard.
((BaseActivity) mContext).setRootsDrawerOpen(true);
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
index 3960475..0709652 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
@@ -298,6 +298,10 @@
return (flags & Root.FLAG_SUPPORTS_SEARCH) != 0;
}
+ public boolean isAdvanced() {
+ return (flags & Root.FLAG_ADVANCED) != 0;
+ }
+
public boolean isLocalOnly() {
return (flags & Root.FLAG_LOCAL_ONLY) != 0;
}
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 9a51b05..62f33bf 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -196,6 +196,7 @@
if (volume.isPrimary()) {
// save off the primary volume for subsequent "Home" dir initialization.
primaryVolume = volume;
+ root.flags |= Root.FLAG_ADVANCED;
}
// Dunno when this would NOT be the case, but never hurts to be correct.
if (volume.isMountedWritable()) {
diff --git a/packages/MtpDocumentsProvider/Android.mk b/packages/MtpDocumentsProvider/Android.mk
index b31b0b1..0f945ee8 100644
--- a/packages/MtpDocumentsProvider/Android.mk
+++ b/packages/MtpDocumentsProvider/Android.mk
@@ -9,5 +9,10 @@
LOCAL_JNI_SHARED_LIBRARIES := libappfuse_jni
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
+# Only enable asserts on userdebug/eng builds
+ifneq (,$(filter userdebug eng, $(TARGET_BUILD_VARIANT)))
+LOCAL_JACK_FLAGS += -D jack.assert.policy=enable
+endif
+
include $(BUILD_PACKAGE)
include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/packages/MtpDocumentsProvider/res/values/strings.xml b/packages/MtpDocumentsProvider/res/values/strings.xml
index f3a3fcf..4a94ef4 100644
--- a/packages/MtpDocumentsProvider/res/values/strings.xml
+++ b/packages/MtpDocumentsProvider/res/values/strings.xml
@@ -16,7 +16,7 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Title of the external storage application [CHAR LIMIT=32] -->
- <string name="app_label">Files</string>
+ <string name="app_label">MTP Host</string>
<!-- Name of MTP root shown in UI. Please align the two strings (device
model and storage name) in proper order in the language.
[CHAR LIMIT=32] -->
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
index 0705214..68f426f 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
@@ -69,7 +69,8 @@
*/
synchronized Cursor queryChildDocuments(String[] columnNames, Identifier parent)
throws IOException {
- Preconditions.checkArgument(parent.mDeviceId == mDevice.deviceId);
+ assert parent.mDeviceId == mDevice.deviceId;
+
LoaderTask task = mTaskList.findTask(parent);
if (task == null) {
if (parent.mDocumentId == null) {
@@ -81,11 +82,9 @@
// 3. startAddingChildDocuemnts.
// 4. stopAddingChildDocuments - It removes the new document added at the step 2,
// because it is not updated between start/stopAddingChildDocuments.
- task = LoaderTask.create(mDatabase, mMtpManager, mDevice.operationsSupported, parent);
- task.fillDocuments(loadDocuments(
- mMtpManager,
- parent.mDeviceId,
- task.getUnloadedObjectHandles(NUM_INITIAL_ENTRIES)));
+ task = new LoaderTask(mMtpManager, mDatabase, mDevice.operationsSupported, parent);
+ task.loadObjectHandles();
+ task.loadObjectInfoList(NUM_INITIAL_ENTRIES);
} else {
// Once remove the existing task in order to add it to the head of the list.
mTaskList.remove(task);
@@ -130,15 +129,11 @@
Preconditions.checkState(existingTask.getState() != LoaderTask.STATE_LOADING);
mTaskList.remove(existingTask);
}
- try {
- final LoaderTask newTask = LoaderTask.create(
- mDatabase, mMtpManager, mDevice.operationsSupported, identifier);
- mTaskList.addFirst(newTask);
- return newTask;
- } catch (IOException exception) {
- Log.e(MtpDocumentsProvider.TAG, "Failed to create a task for mapping", exception);
- // Continue to release the background thread.
- }
+ final LoaderTask newTask = new LoaderTask(
+ mMtpManager, mDatabase, mDevice.operationsSupported, identifier);
+ newTask.loadObjectHandles();
+ mTaskList.addFirst(newTask);
+ return newTask;
}
mBackgroundThread = null;
@@ -170,24 +165,6 @@
}
/**
- * Helper method to loads multiple object info.
- */
- private static MtpObjectInfo[] loadDocuments(MtpManager manager, int deviceId, int[] handles)
- throws IOException {
- final ArrayList<MtpObjectInfo> objects = new ArrayList<>();
- for (int i = 0; i < handles.length; i++) {
- final MtpObjectInfo info = manager.getObjectInfo(deviceId, handles[i]);
- if (info == null) {
- Log.e(MtpDocumentsProvider.TAG,
- "Failed to obtain object info handle=" + handles[i]);
- continue;
- }
- objects.add(info);
- }
- return objects.toArray(new MtpObjectInfo[objects.size()]);
- }
-
- /**
* Background thread to fetch object info.
*/
private class BackgroundLoaderThread extends Thread {
@@ -203,21 +180,13 @@
if (task == null) {
return;
}
- try {
- final MtpObjectInfo[] objectInfos = loadDocuments(
- mMtpManager,
- task.mIdentifier.mDeviceId,
- task.getUnloadedObjectHandles(NUM_LOADING_ENTRIES));
- task.fillDocuments(objectInfos);
- final boolean shouldNotify =
- task.mLastNotified.getTime() <
- new Date().getTime() - NOTIFY_PERIOD_MS ||
- task.getState() != LoaderTask.STATE_LOADING;
- if (shouldNotify) {
- task.notify(mResolver);
- }
- } catch (IOException exception) {
- task.setError(exception);
+ task.loadObjectInfoList(NUM_LOADING_ENTRIES);
+ final boolean shouldNotify =
+ task.mLastNotified.getTime() <
+ new Date().getTime() - NOTIFY_PERIOD_MS ||
+ task.getState() != LoaderTask.STATE_LOADING;
+ if (shouldNotify) {
+ task.notify(mResolver);
}
}
}
@@ -271,43 +240,67 @@
* Each task is responsible for fetching child documents for the given parent document.
*/
private static class LoaderTask {
- static final int STATE_LOADING = 0;
- static final int STATE_COMPLETED = 1;
- static final int STATE_ERROR = 2;
+ static final int STATE_START = 0;
+ static final int STATE_LOADING = 1;
+ static final int STATE_COMPLETED = 2;
+ static final int STATE_ERROR = 3;
+ final MtpManager mManager;
final MtpDatabase mDatabase;
final int[] mOperationsSupported;
final Identifier mIdentifier;
- final int[] mObjectHandles;
+ int[] mObjectHandles;
+ int mState;
Date mLastNotified;
- int mNumLoaded;
- Exception mError;
+ int mPosition;
+ IOException mError;
- LoaderTask(MtpDatabase database, int[] operationsSupported, Identifier identifier,
- int[] objectHandles) {
- Preconditions.checkNotNull(operationsSupported);
- Preconditions.checkNotNull(objectHandles);
+ LoaderTask(MtpManager manager, MtpDatabase database, int[] operationsSupported,
+ Identifier identifier) {
+ assert operationsSupported != null;
+ assert identifier.mDocumentType != MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE;
+ mManager = manager;
mDatabase = database;
mOperationsSupported = operationsSupported;
mIdentifier = identifier;
- mObjectHandles = objectHandles;
- mNumLoaded = 0;
+ mObjectHandles = null;
+ mState = STATE_START;
+ mPosition = 0;
mLastNotified = new Date();
}
+ synchronized void loadObjectHandles() {
+ assert mState == STATE_START;
+ int parentHandle = mIdentifier.mObjectHandle;
+ // Need to pass the special value MtpManager.OBJECT_HANDLE_ROOT_CHILDREN to
+ // getObjectHandles if we would like to obtain children under the root.
+ if (mIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE) {
+ parentHandle = MtpManager.OBJECT_HANDLE_ROOT_CHILDREN;
+ }
+ try {
+ mObjectHandles = mManager.getObjectHandles(
+ mIdentifier.mDeviceId, mIdentifier.mStorageId, parentHandle);
+ mState = STATE_LOADING;
+ } catch (IOException error) {
+ mError = error;
+ mState = STATE_ERROR;
+ }
+ }
+
/**
* Returns a cursor that traverses the child document of the parent document handled by the
* task.
* The returned task may have a EXTRA_LOADING flag.
*/
- Cursor createCursor(ContentResolver resolver, String[] columnNames) throws IOException {
+ synchronized Cursor createCursor(ContentResolver resolver, String[] columnNames)
+ throws IOException {
final Bundle extras = new Bundle();
switch (getState()) {
case STATE_LOADING:
extras.putBoolean(DocumentsContract.EXTRA_LOADING, true);
break;
case STATE_ERROR:
- throw new IOException(mError);
+ throw mError;
}
final Cursor cursor =
@@ -319,26 +312,67 @@
}
/**
- * Returns a state of the task.
+ * Stores object information into database.
*/
- int getState() {
- if (mError != null) {
- return STATE_ERROR;
- } else if (mNumLoaded == mObjectHandles.length) {
- return STATE_COMPLETED;
- } else {
- return STATE_LOADING;
+ void loadObjectInfoList(int count) {
+ synchronized (this) {
+ if (mState != STATE_LOADING) {
+ return;
+ }
+ if (mPosition == 0) {
+ try{
+ mDatabase.getMapper().startAddingDocuments(mIdentifier.mDocumentId);
+ } catch (FileNotFoundException error) {
+ mError = error;
+ mState = STATE_ERROR;
+ return;
+ }
+ }
+ }
+ final ArrayList<MtpObjectInfo> infoList = new ArrayList<>();
+ for (int chunkEnd = mPosition + count;
+ mPosition < mObjectHandles.length && mPosition < chunkEnd;
+ mPosition++) {
+ try {
+ infoList.add(mManager.getObjectInfo(
+ mIdentifier.mDeviceId, mObjectHandles[mPosition]));
+ } catch (IOException error) {
+ Log.e(MtpDocumentsProvider.TAG, "Failed to load object info", error);
+ }
+ }
+ synchronized (this) {
+ try {
+ mDatabase.getMapper().putChildDocuments(
+ mIdentifier.mDeviceId,
+ mIdentifier.mDocumentId,
+ mOperationsSupported,
+ infoList.toArray(new MtpObjectInfo[infoList.size()]));
+ } catch (FileNotFoundException error) {
+ // Looks like the parent document information is removed.
+ // Adding documents has already cancelled in Mapper so we don't need to invoke
+ // stopAddingDocuments.
+ mError = error;
+ mState = STATE_ERROR;
+ return;
+ }
+ if (mPosition >= mObjectHandles.length) {
+ try{
+ mDatabase.getMapper().stopAddingDocuments(mIdentifier.mDocumentId);
+ mState = STATE_COMPLETED;
+ } catch (FileNotFoundException error) {
+ mError = error;
+ mState = STATE_ERROR;
+ return;
+ }
+ }
}
}
/**
- * Obtains object handles that have not been loaded yet.
+ * Returns a state of the task.
*/
- int[] getUnloadedObjectHandles(int count) {
- return Arrays.copyOfRange(
- mObjectHandles,
- mNumLoaded,
- Math.min(mNumLoaded + count, mObjectHandles.length));
+ int getState() {
+ return mState;
}
/**
@@ -349,69 +383,9 @@
mLastNotified = new Date();
}
- /**
- * Stores object information into database.
- */
- void fillDocuments(MtpObjectInfo[] objectInfoList) {
- if (objectInfoList.length == 0 || getState() != STATE_LOADING) {
- return;
- }
- try{
- if (mNumLoaded == 0) {
- mDatabase.getMapper().startAddingDocuments(mIdentifier.mDocumentId);
- }
- mDatabase.getMapper().putChildDocuments(
- mIdentifier.mDeviceId, mIdentifier.mDocumentId, mOperationsSupported,
- objectInfoList);
- mNumLoaded += objectInfoList.length;
- if (getState() != STATE_LOADING) {
- mDatabase.getMapper().stopAddingDocuments(mIdentifier.mDocumentId);
- }
- } catch (FileNotFoundException exception) {
- setErrorInternal(exception);
- }
- }
-
- /**
- * Marks the loading task as error.
- */
- void setError(Exception error) {
- final int lastState = getState();
- setErrorInternal(error);
- if (lastState == STATE_LOADING) {
- try {
- mDatabase.getMapper().stopAddingDocuments(mIdentifier.mDocumentId);
- } catch (FileNotFoundException exception) {
- setErrorInternal(exception);
- }
- }
- }
-
- private void setErrorInternal(Exception error) {
- Log.e(MtpDocumentsProvider.TAG, "Error in DocumentLoader thread", error);
- mError = error;
- mNumLoaded = 0;
- }
-
private Uri createUri() {
return DocumentsContract.buildChildDocumentsUri(
MtpDocumentsProvider.AUTHORITY, mIdentifier.mDocumentId);
}
-
- /**
- * Creates a LoaderTask that loads children of the given document.
- */
- static LoaderTask create(MtpDatabase database, MtpManager manager,
- int[] operationsSupported, Identifier parent)
- throws IOException {
- int parentHandle = parent.mObjectHandle;
- // Need to pass the special value MtpManager.OBJECT_HANDLE_ROOT_CHILDREN to
- // getObjectHandles if we would like to obtain children under the root.
- if (parent.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE) {
- parentHandle = MtpManager.OBJECT_HANDLE_ROOT_CHILDREN;
- }
- return new LoaderTask(database, operationsSupported, parent, manager.getObjectHandles(
- parent.mDeviceId, parent.mStorageId, parentHandle));
- }
}
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
index 8c73211..4564018 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
@@ -617,6 +617,7 @@
final String whereClosure =
"parent." + COLUMN_DEVICE_ID + " = ? AND " +
"parent." + COLUMN_ROW_STATE + " IN (?, ?) AND " +
+ "parent." + COLUMN_DOCUMENT_TYPE + " != ? AND " +
"child." + COLUMN_ROW_STATE + " = ?";
try (final Cursor cursor = mDatabase.query(
fromClosure,
@@ -626,7 +627,7 @@
"parent." + Document.COLUMN_DOCUMENT_ID,
"parent." + COLUMN_DOCUMENT_TYPE),
whereClosure,
- strings(deviceId, ROW_STATE_VALID, ROW_STATE_INVALIDATED,
+ strings(deviceId, ROW_STATE_VALID, ROW_STATE_INVALIDATED, DOCUMENT_TYPE_DEVICE,
ROW_STATE_DISCONNECTED),
null,
null,
@@ -750,7 +751,12 @@
values.putNull(Document.COLUMN_SUMMARY);
values.putNull(Document.COLUMN_LAST_MODIFIED);
values.put(Document.COLUMN_ICON, R.drawable.ic_root_mtp);
- values.put(Document.COLUMN_FLAGS, 0);
+ values.put(Document.COLUMN_FLAGS, getDocumentFlags(
+ device.operationsSupported,
+ Document.MIME_TYPE_DIR,
+ 0,
+ MtpConstants.PROTECTION_STATUS_NONE,
+ DOCUMENT_TYPE_DEVICE));
values.putNull(Document.COLUMN_SIZE);
extraValues.clear();
@@ -765,7 +771,7 @@
* @param values {@link ContentValues} that receives values.
* @param extraValues {@link ContentValues} that receives extra values for roots.
* @param parentDocumentId Parent document ID.
- * @param supportedOperations Array of Operation code supported by the device.
+ * @param operationsSupported Array of Operation code supported by the device.
* @param root Root to be converted {@link ContentValues}.
*/
static void getStorageDocumentValues(
@@ -786,7 +792,12 @@
values.putNull(Document.COLUMN_SUMMARY);
values.putNull(Document.COLUMN_LAST_MODIFIED);
values.put(Document.COLUMN_ICON, R.drawable.ic_root_mtp);
- values.put(Document.COLUMN_FLAGS, 0);
+ values.put(Document.COLUMN_FLAGS, getDocumentFlags(
+ operationsSupported,
+ Document.MIME_TYPE_DIR,
+ 0,
+ MtpConstants.PROTECTION_STATUS_NONE,
+ DOCUMENT_TYPE_STORAGE));
values.put(Document.COLUMN_SIZE, root.mMaxCapacity - root.mFreeSpace);
extraValues.put(Root.COLUMN_FLAGS, getRootFlags(operationsSupported));
@@ -803,8 +814,8 @@
* @param info MTP object info.
*/
static void getObjectDocumentValues(
- ContentValues values, int deviceId, String parentId, int[] operationsSupported,
- MtpObjectInfo info) {
+ ContentValues values, int deviceId, String parentId,
+ int[] operationsSupported, MtpObjectInfo info) {
values.clear();
final String mimeType = getMimeType(info);
values.put(COLUMN_DEVICE_ID, deviceId);
@@ -822,7 +833,7 @@
values.putNull(Document.COLUMN_ICON);
values.put(Document.COLUMN_FLAGS, getDocumentFlags(
operationsSupported, mimeType, info.getThumbCompressedSizeLong(),
- info.getProtectionStatus()));
+ info.getProtectionStatus(), DOCUMENT_TYPE_OBJECT));
values.put(Document.COLUMN_SIZE, info.getCompressedSizeLong());
}
@@ -861,16 +872,19 @@
}
private static int getDocumentFlags(
- int[] operationsSupported, String mimeType, long thumbnailSize, int protectionState) {
+ @Nullable int[] operationsSupported, String mimeType, long thumbnailSize,
+ int protectionState, @DocumentType int documentType) {
int flag = 0;
- if (MtpDeviceRecord.isWritingSupported(operationsSupported) &&
+ if (!mimeType.equals(Document.MIME_TYPE_DIR) &&
+ MtpDeviceRecord.isWritingSupported(operationsSupported) &&
protectionState == MtpConstants.PROTECTION_STATUS_NONE) {
flag |= Document.FLAG_SUPPORTS_WRITE;
}
if (MtpDeviceRecord.isSupported(
operationsSupported, MtpConstants.OPERATION_DELETE_OBJECT) &&
(protectionState == MtpConstants.PROTECTION_STATUS_NONE ||
- protectionState == MtpConstants.PROTECTION_STATUS_NON_TRANSFERABLE_DATA)) {
+ protectionState == MtpConstants.PROTECTION_STATUS_NON_TRANSFERABLE_DATA) &&
+ documentType == DOCUMENT_TYPE_OBJECT) {
flag |= Document.FLAG_SUPPORTS_DELETE;
}
if (mimeType.equals(Document.MIME_TYPE_DIR) &&
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
index 68c1992..cf5bee5 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
@@ -360,8 +360,12 @@
if (i == 0) {
infoUniqueName = info;
} else {
- infoUniqueName = new MtpObjectInfo.Builder(info).setName(
- baseName + " (" + i + ")." + extension).build();
+ String suffixedName = baseName + " (" + i + " )";
+ if (!extension.isEmpty()) {
+ suffixedName += "." + extension;
+ }
+ infoUniqueName =
+ new MtpObjectInfo.Builder(info).setName(suffixedName).build();
}
try {
objectHandle = mMtpManager.createDocument(
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java
index 6fb2a78..0599b70 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java
@@ -130,11 +130,14 @@
return devices.toArray(new MtpDeviceRecord[devices.size()]);
}
- MtpObjectInfo getObjectInfo(int deviceId, int objectHandle)
- throws IOException {
+ MtpObjectInfo getObjectInfo(int deviceId, int objectHandle) throws IOException {
final MtpDevice device = getDevice(deviceId);
synchronized (device) {
- return device.getObjectInfo(objectHandle);
+ final MtpObjectInfo info = device.getObjectInfo(objectHandle);
+ if (info == null) {
+ throw new IOException("Failed to get object info: " + objectHandle);
+ }
+ return info;
}
}
@@ -291,7 +294,7 @@
if (usbInterface.getInterfaceClass() == UsbConstants.USB_SUBCLASS_VENDOR_SPEC &&
usbInterface.getInterfaceSubclass() == SUBCLASS_MTP &&
usbInterface.getInterfaceProtocol() == PROTOCOL_MTP &&
- usbInterface.getName().equals("MTP")) {
+ "MTP".equals(usbInterface.getName())) {
return true;
}
}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
index db25421..45f89e4 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
@@ -55,13 +55,6 @@
mManager = new BlockableTestMtpManager(getContext());
mResolver = new TestContentResolver();
- mLoader = new DocumentLoader(
- new MtpDeviceRecord(
- 0, "Device", "Key", true, new MtpRoot[0],
- TestUtil.OPERATIONS_SUPPORTED, new int[0]),
- mManager,
- mResolver,
- mDatabase);
}
@Override
@@ -71,6 +64,8 @@
}
public void testBasic() throws Exception {
+ setUpLoader();
+
final Uri uri = DocumentsContract.buildChildDocumentsUri(
MtpDocumentsProvider.AUTHORITY, mParentIdentifier.mDocumentId);
setUpDocument(mManager, 40);
@@ -107,6 +102,55 @@
assertEquals(2, mResolver.getChangeCount(uri));
}
+ public void testError_GetObjectHandles() throws Exception {
+ mManager = new BlockableTestMtpManager(getContext()) {
+ @Override
+ int[] getObjectHandles(int deviceId, int storageId, int parentObjectHandle)
+ throws IOException {
+ throw new IOException();
+ }
+ };
+ setUpLoader();
+ mManager.setObjectHandles(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, null);
+ try {
+ try (final Cursor cursor = mLoader.queryChildDocuments(
+ MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION, mParentIdentifier)) {}
+ fail();
+ } catch (IOException exception) {
+ // Expect exception.
+ }
+ }
+
+ public void testError_GetObjectInfo() throws Exception {
+ mManager = new BlockableTestMtpManager(getContext()) {
+ @Override
+ MtpObjectInfo getObjectInfo(int deviceId, int objectHandle) throws IOException {
+ if (objectHandle == DocumentLoader.NUM_INITIAL_ENTRIES) {
+ throw new IOException();
+ } else {
+ return super.getObjectInfo(deviceId, objectHandle);
+ }
+ }
+ };
+ setUpLoader();
+ setUpDocument(mManager, DocumentLoader.NUM_INITIAL_ENTRIES);
+ try (final Cursor cursor = mLoader.queryChildDocuments(
+ MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION, mParentIdentifier)) {
+ // Even if MtpManager returns an error for a document, loading must complete.
+ assertFalse(cursor.getExtras().getBoolean(DocumentsContract.EXTRA_LOADING));
+ }
+ }
+
+ private void setUpLoader() {
+ mLoader = new DocumentLoader(
+ new MtpDeviceRecord(
+ 0, "Device", "Key", true, new MtpRoot[0],
+ TestUtil.OPERATIONS_SUPPORTED, new int[0]),
+ mManager,
+ mResolver,
+ mDatabase);
+ }
+
private void setUpDocument(TestMtpManager manager, int count) {
int[] childDocuments = new int[count];
for (int i = 0; i < childDocuments.length; i++) {
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
index b74069a..284223b 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
@@ -103,7 +103,7 @@
assertTrue(isNull(cursor, COLUMN_SUMMARY));
assertTrue(isNull(cursor, COLUMN_LAST_MODIFIED));
assertEquals(R.drawable.ic_root_mtp, getInt(cursor, COLUMN_ICON));
- assertEquals(0, getInt(cursor, COLUMN_FLAGS));
+ assertEquals(Document.FLAG_DIR_SUPPORTS_CREATE, getInt(cursor, COLUMN_FLAGS));
assertEquals(1000, getInt(cursor, COLUMN_SIZE));
assertEquals(
MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE,
@@ -165,7 +165,7 @@
assertTrue(isNull(cursor, COLUMN_SUMMARY));
assertTrue(isNull(cursor, COLUMN_LAST_MODIFIED));
assertEquals(R.drawable.ic_root_mtp, getInt(cursor, COLUMN_ICON));
- assertEquals(0, getInt(cursor, COLUMN_FLAGS));
+ assertEquals(Document.FLAG_DIR_SUPPORTS_CREATE, getInt(cursor, COLUMN_FLAGS));
assertEquals(1000, getInt(cursor, COLUMN_SIZE));
assertEquals(
MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE, getInt(cursor, COLUMN_DOCUMENT_TYPE));
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index 9c1880a..da6af37 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -351,7 +351,6 @@
assertEquals(1422716400000L, cursor.getLong(3));
assertEquals(
DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
- DocumentsContract.Document.FLAG_SUPPORTS_WRITE |
DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE,
cursor.getInt(4));
assertEquals(0, cursor.getInt(5));
@@ -419,20 +418,16 @@
try {
mProvider.queryChildDocuments("1", null, null);
fail();
- } catch (Throwable error) {
- assertTrue(error instanceof FileNotFoundException);
- }
+ } catch (FileNotFoundException error) {}
}
public void testQueryChildDocuments_documentError() throws Exception {
setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
mMtpManager.setObjectHandles(0, 0, -1, new int[] { 1 });
- try {
- mProvider.queryChildDocuments("1", null, null);
- fail();
- } catch (Throwable error) {
- assertTrue(error instanceof FileNotFoundException);
+ try (final Cursor cursor = mProvider.queryChildDocuments("1", null, null)) {
+ assertEquals(0, cursor.getCount());
+ assertFalse(cursor.getExtras().getBoolean(DocumentsContract.EXTRA_LOADING));
}
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index 4e1180d..bb34fcf 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -159,7 +159,6 @@
private static final int STATE_PRINTER_UNAVAILABLE = 6;
private static final int STATE_UPDATE_SLOW = 7;
private static final int STATE_PRINT_COMPLETED = 8;
- private static final int STATE_FINISHING = 9;
private static final int UI_STATE_PREVIEW = 0;
private static final int UI_STATE_ERROR = 1;
@@ -255,6 +254,9 @@
/** Whether at least one print services is enabled or not */
private boolean mArePrintServicesEnabled;
+ /** Is doFinish() already in progress */
+ private boolean mIsFinishing;
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -2035,11 +2037,11 @@
return;
}
- if (mState == STATE_FINISHING) {
+ if (mIsFinishing) {
return;
}
- mState = STATE_FINISHING;
+ mIsFinishing = true;
if (mPrinterRegistry != null) {
mPrinterRegistry.setTrackedPrinter(null);
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 1aee490..a2a3fd3 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -793,4 +793,23 @@
<!-- Label for length of time until battery is charged [CHAR LIMIT=20] -->
<string name="remaining_length_format"><xliff:g name="time" example="3 hours">%1$s</xliff:g> left</string>
+ <!-- Hint text for the IP address -->
+ <string name="wifi_ip_address_hint" translatable="false">192.168.1.128</string>
+ <!-- Hint text for DNS -->
+ <string name="wifi_dns1_hint" translatable="false">8.8.8.8</string>
+ <!-- Hint text for DNS -->
+ <string name="wifi_dns2_hint" translatable="false">8.8.4.4</string>
+ <!-- Hint text for the gateway -->
+ <string name="wifi_gateway_hint" translatable="false">192.168.1.1</string>
+ <!-- Hint text for network prefix length -->
+ <string name="wifi_network_prefix_length_hint" translatable="false">24</string>
+ <!-- HTTP proxy settings. The hint text field for port. -->
+ <string name="proxy_port_hint" translatable="false">8080</string>
+ <!-- HTTP proxy settings. Hint for Proxy-Auto Config URL. -->
+ <string name="proxy_url_hint" translatable="false">https://www.example.com/proxy.pac</string>
+ <!-- HTTP proxy settings. The hint text for proxy exclusion list. -->
+ <string name="proxy_exclusionlist_hint" translatable="false">example.com,mycomp.test.com,localhost</string>
+ <!-- HTTP proxy settings. The hint text field for the hostname. -->
+ <string name="proxy_hostname_hint" translatable="false">proxy.example.com</string>
+
</resources>
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 7fae0ee..c9582ea 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -149,6 +149,10 @@
// Passed to Message.obtain() when msg.arg2 is not used.
private static final int UNUSED_ARG2 = -2;
+ // Maximum progress displayed (like 99.00%).
+ private static final int CAPPED_PROGRESS = 9900;
+ private static final int CAPPED_MAX = 10000;
+
/**
* Delay before a screenshot is taken.
* <p>
@@ -427,7 +431,7 @@
final NumberFormat nf = NumberFormat.getPercentInstance();
nf.setMinimumFractionDigits(2);
nf.setMaximumFractionDigits(2);
- final String percentText = nf.format((double) info.progress / info.max);
+ final String percentageText = nf.format((double) info.progress / info.max);
final Action cancelAction = new Action.Builder(null, mContext.getString(
com.android.internal.R.string.cancel), newCancelIntent(mContext, info)).build();
final Intent infoIntent = new Intent(mContext, BugreportProgressService.class);
@@ -458,7 +462,7 @@
.setContentTitle(title)
.setTicker(title)
.setContentText(name)
- .setContentInfo(percentText)
+ .setContentInfo(percentageText)
.setProgress(info.max, info.progress, false)
.setOngoing(true)
.setContentIntent(infoPendingIntent)
@@ -472,7 +476,7 @@
}
if (DEBUG) {
Log.d(TAG, "Sending 'Progress' notification for id " + info.id + "(pid " + info.pid
- + "): " + percentText);
+ + "): " + percentageText);
}
NotificationManager.from(mContext).notify(TAG, info.id, notification);
}
@@ -545,25 +549,47 @@
}
activeProcesses++;
final String progressKey = DUMPSTATE_PREFIX + pid + PROGRESS_SUFFIX;
- final int progress = SystemProperties.getInt(progressKey, 0);
- if (progress == 0) {
+ info.realProgress = SystemProperties.getInt(progressKey, 0);
+ if (info.realProgress == 0) {
Log.v(TAG, "System property " + progressKey + " is not set yet");
}
- final int max = SystemProperties.getInt(DUMPSTATE_PREFIX + pid + MAX_SUFFIX, 0);
- final boolean maxChanged = max > 0 && max != info.max;
- final boolean progressChanged = progress > 0 && progress != info.progress;
+ final String maxKey = DUMPSTATE_PREFIX + pid + MAX_SUFFIX;
+ info.realMax = SystemProperties.getInt(maxKey, info.max);
+ if (info.realMax <= 0 ) {
+ Log.w(TAG, "Property " + maxKey + " is not positive: " + info.max);
+ continue;
+ }
+ /*
+ * Checks whether the progress changed in a way that should be displayed to the user:
+ * - info.progress / info.max represents the displayed progress
+ * - info.realProgress / info.realMax represents the real progress
+ * - since the real progress can decrease, the displayed progress is only updated if it
+ * increases
+ * - the displayed progress is capped at a maximum (like 99%)
+ */
+ final int oldPercentage = (CAPPED_MAX * info.progress) / info.max;
+ int newPercentage = (CAPPED_MAX * info.realProgress) / info.realMax;
+ int max = info.realMax;
+ int progress = info.realProgress;
- if (progressChanged || maxChanged) {
- if (progressChanged) {
- if (DEBUG) Log.v(TAG, "Updating progress for PID " + pid + "(id: " + id
- + ") from " + info.progress + " to " + progress);
- info.progress = progress;
+ if (newPercentage > CAPPED_PROGRESS) {
+ progress = newPercentage = CAPPED_PROGRESS;
+ max = CAPPED_MAX;
+ }
+
+ if (newPercentage > oldPercentage) {
+ if (DEBUG) {
+ if (progress != info.progress) {
+ Log.v(TAG, "Updating progress for PID " + pid + "(id: " + id + ") from "
+ + info.progress + " to " + progress);
+ }
+ if (max != info.max) {
+ Log.v(TAG, "Updating max progress for PID " + pid + "(id: " + id + ") from "
+ + info.max + " to " + max);
+ }
}
- if (maxChanged) {
- Log.i(TAG, "Updating max progress for PID " + pid + "(id: " + id
- + ") from " + info.max + " to " + max);
- info.max = max;
- }
+ info.progress = progress;
+ info.max = max;
info.lastUpdate = System.currentTimeMillis();
updateProgress(info);
} else {
@@ -1450,16 +1476,26 @@
String description;
/**
- * Maximum progress of the bugreport generation.
+ * Maximum progress of the bugreport generation as displayed by the UI.
*/
int max;
/**
- * Current progress of the bugreport generation.
+ * Current progress of the bugreport generation as displayed by the UI.
*/
int progress;
/**
+ * Maximum progress of the bugreport generation as reported by dumpstate.
+ */
+ int realMax;
+
+ /**
+ * Current progress of the bugreport generation as reported by dumpstate.
+ */
+ int realProgress;
+
+ /**
* Time of the last progress update.
*/
long lastUpdate = System.currentTimeMillis();
@@ -1568,10 +1604,12 @@
@Override
public String toString() {
final float percent = ((float) progress * 100 / max);
+ final float realPercent = ((float) realProgress * 100 / realMax);
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 + ")"
+ + "\n\treal progress: " + realProgress + "/" + realMax + " (" + realPercent + ")"
+ "\n\tlast_update: " + getFormattedLastUpdate()
+ "\naddingDetailsToZip: " + addingDetailsToZip
+ " addedDetailsToZip: " + addedDetailsToZip;
@@ -1587,6 +1625,8 @@
description = in.readString();
max = in.readInt();
progress = in.readInt();
+ realMax = in.readInt();
+ realProgress = in.readInt();
lastUpdate = in.readLong();
formattedLastUpdate = in.readString();
bugreportFile = readFile(in);
@@ -1609,6 +1649,8 @@
dest.writeString(description);
dest.writeInt(max);
dest.writeInt(progress);
+ dest.writeInt(realMax);
+ dest.writeInt(realProgress);
dest.writeLong(lastUpdate);
dest.writeString(getFormattedLastUpdate());
writeFile(dest, bugreportFile);
diff --git a/packages/Shell/src/com/android/shell/BugreportStorageProvider.java b/packages/Shell/src/com/android/shell/BugreportStorageProvider.java
index 49759c5..814aa8c 100644
--- a/packages/Shell/src/com/android/shell/BugreportStorageProvider.java
+++ b/packages/Shell/src/com/android/shell/BugreportStorageProvider.java
@@ -56,7 +56,7 @@
final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
final RowBuilder row = result.newRow();
row.add(Root.COLUMN_ROOT_ID, DOC_ID_ROOT);
- row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY);
+ row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED);
row.add(Root.COLUMN_ICON, android.R.mipmap.sym_def_app_icon);
row.add(Root.COLUMN_TITLE, getContext().getString(R.string.bugreport_storage_title));
row.add(Root.COLUMN_DOCUMENT_ID, DOC_ID_ROOT);
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index 17f6f6b..e1e0c3b 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -26,6 +26,7 @@
import static com.android.shell.BugreportProgressService.EXTRA_SCREENSHOT;
import static com.android.shell.BugreportProgressService.INTENT_BUGREPORT_FINISHED;
import static com.android.shell.BugreportProgressService.INTENT_BUGREPORT_STARTED;
+import static com.android.shell.BugreportProgressService.POLLING_FREQUENCY;
import static com.android.shell.BugreportProgressService.SCREENSHOT_DELAY_SECONDS;
import java.io.BufferedOutputStream;
@@ -92,7 +93,7 @@
private static final String TAG = "BugreportReceiverTest";
// Timeout for UI operations, in milliseconds.
- private static final int TIMEOUT = (int) BugreportProgressService.POLLING_FREQUENCY * 4;
+ private static final int TIMEOUT = (int) POLLING_FREQUENCY * 4;
// Timeout for when waiting for a screenshot to finish.
private static final int SAFE_SCREENSHOT_DELAY = SCREENSHOT_DELAY_SECONDS + 10;
@@ -190,8 +191,30 @@
SystemProperties.set(PROGRESS_PROPERTY, "500");
assertProgressNotification(NAME, nf.format(0.50));
+ SystemProperties.set(PROGRESS_PROPERTY, "950");
+ assertProgressNotification(NAME, nf.format(0.95));
+
+ // Make sure progress never goes back...
SystemProperties.set(MAX_PROPERTY, "2000");
- assertProgressNotification(NAME, nf.format(0.25));
+ Thread.sleep(POLLING_FREQUENCY + DateUtils.SECOND_IN_MILLIS);
+ assertProgressNotification(NAME, nf.format(0.95));
+
+ SystemProperties.set(PROGRESS_PROPERTY, "1000");
+ assertProgressNotification(NAME, nf.format(0.95));
+
+ // ...only forward...
+ SystemProperties.set(PROGRESS_PROPERTY, "1902");
+ assertProgressNotification(NAME, nf.format(0.9510));
+
+ SystemProperties.set(PROGRESS_PROPERTY, "1960");
+ assertProgressNotification(NAME, nf.format(0.98));
+
+ // ...but never more than the capped value.
+ SystemProperties.set(PROGRESS_PROPERTY, "2000");
+ assertProgressNotification(NAME, nf.format(0.99));
+
+ SystemProperties.set(PROGRESS_PROPERTY, "3000");
+ assertProgressNotification(NAME, nf.format(0.99));
Bundle extras =
sendBugreportFinishedAndGetSharedIntent(ID, mPlainTextPath, mScreenshotPath);
diff --git a/packages/Shell/tests/src/com/android/shell/UiBot.java b/packages/Shell/tests/src/com/android/shell/UiBot.java
index 384c3da..5bfe1a0 100644
--- a/packages/Shell/tests/src/com/android/shell/UiBot.java
+++ b/packages/Shell/tests/src/com/android/shell/UiBot.java
@@ -32,7 +32,7 @@
final class UiBot {
private static final String TAG = "UiBot";
- private static final String SYSTEMUI_PACKAGED = "com.android.systemui";
+ private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
private final UiDevice mDevice;
private final int mTimeout;
@@ -51,8 +51,8 @@
public UiObject getNotification(String text) {
boolean opened = mDevice.openNotification();
Log.v(TAG, "openNotification(): " + opened);
- boolean gotIt = mDevice.wait(Until.hasObject(By.pkg(SYSTEMUI_PACKAGED)), mTimeout);
- assertTrue("could not get system ui (" + SYSTEMUI_PACKAGED + ")", gotIt);
+ boolean gotIt = mDevice.wait(Until.hasObject(By.pkg(SYSTEMUI_PACKAGE)), mTimeout);
+ assertTrue("could not get system ui (" + SYSTEMUI_PACKAGE + ")", gotIt);
return getObject(text);
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 073cf14..589eac6 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -156,6 +156,9 @@
<!-- TV picture-in-picture -->
<uses-permission android:name="android.permission.RECEIVE_MEDIA_RESOURCE_USAGE" />
+ <!-- DND access -->
+ <uses-permission android:name="android.permission.MANAGE_NOTIFICATIONS" />
+
<application
android:name=".SystemUIApplication"
android:persistent="true"
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_cancel_white_24dp.png b/packages/SystemUI/res/drawable-hdpi/ic_cancel_white_24dp.png
new file mode 100644
index 0000000..73f5116
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/ic_cancel_white_24dp.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_cancel_white_24dp.png b/packages/SystemUI/res/drawable-mdpi/ic_cancel_white_24dp.png
new file mode 100644
index 0000000..787e259
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_cancel_white_24dp.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_cancel_white_24dp.png b/packages/SystemUI/res/drawable-xhdpi/ic_cancel_white_24dp.png
new file mode 100644
index 0000000..6ebbc83
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_cancel_white_24dp.png
Binary files differ
diff --git a/packages/SystemUI/res/layout/hybrid_notification.xml b/packages/SystemUI/res/layout/hybrid_notification.xml
index f667859..476f52b 100644
--- a/packages/SystemUI/res/layout/hybrid_notification.xml
+++ b/packages/SystemUI/res/layout/hybrid_notification.xml
@@ -21,7 +21,7 @@
android:layout_height="wrap_content"
android:paddingStart="@*android:dimen/notification_content_margin_start"
android:paddingEnd="12dp"
- android:gravity="bottom">
+ android:gravity="bottom|start">
<TextView
android:id="@+id/notification_title"
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/hybrid_overflow_number.xml b/packages/SystemUI/res/layout/hybrid_overflow_number.xml
new file mode 100644
index 0000000..f3dde8d
--- /dev/null
+++ b/packages/SystemUI/res/layout/hybrid_overflow_number.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/notification_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@*android:style/TextAppearance.Material.Notification"
+ android:paddingEnd="@*android:dimen/notification_content_margin_end"
+ android:gravity="end"
+ android:singleLine="true"
+ />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/notification_guts.xml b/packages/SystemUI/res/layout/notification_guts.xml
index 1ab6bf9..062ae35 100644
--- a/packages/SystemUI/res/layout/notification_guts.xml
+++ b/packages/SystemUI/res/layout/notification_guts.xml
@@ -31,12 +31,12 @@
<!-- header -->
<LinearLayout
android:layout_width="match_parent"
- android:layout_height="30dp"
- android:paddingTop="9dp"
+ android:layout_height="wrap_content"
+ android:paddingTop="14dp"
android:paddingEnd="8dp"
android:id="@+id/notification_guts_header"
android:orientation="horizontal"
- android:layout_gravity="center_vertical|start">
+ android:layout_gravity="start">
<ImageView
android:id="@+id/app_icon"
@@ -64,7 +64,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="4dp"
- android:paddingBottom="16dip"
android:paddingEnd="8dp" >
<RadioButton
android:id="@+id/silent_importance"
@@ -99,7 +98,6 @@
android:orientation="vertical"
android:clickable="false"
android:focusable="false"
- android:paddingBottom="8dip"
android:paddingEnd="8dp"
android:visibility="gone">
<TextView
@@ -166,13 +164,14 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
+ android:paddingTop="16dp"
android:paddingBottom="8dp" >
<TextView
android:id="@+id/more_settings"
android:text="@string/notification_more_settings"
android:layout_width="wrap_content"
- android:layout_height="48dp"
+ android:layout_height="36dp"
style="@style/TextAppearance.NotificationGuts.Button"
android:background="@drawable/btn_borderless_rect"
android:gravity="center"
@@ -184,7 +183,7 @@
android:id="@+id/done"
android:text="@string/notification_done"
android:layout_width="wrap_content"
- android:layout_height="48dp"
+ android:layout_height="36dp"
style="@style/TextAppearance.NotificationGuts.Button"
android:background="@drawable/btn_borderless_rect"
android:gravity="center"
diff --git a/packages/SystemUI/res/layout/recents_on_tv.xml b/packages/SystemUI/res/layout/recents_on_tv.xml
index 567e009..f0bfebe 100644
--- a/packages/SystemUI/res/layout/recents_on_tv.xml
+++ b/packages/SystemUI/res/layout/recents_on_tv.xml
@@ -28,26 +28,21 @@
android:clipChildren="false"
android:clipToPadding="false"
android:descendantFocusability="beforeDescendants"
- android:layout_gravity="center"
- android:gravity="center"
- android:paddingStart="@dimen/recents_tv_grid_row_padding"
- android:paddingEnd="@dimen/recents_tv_grid_row_padding"
+ android:layout_marginTop="@dimen/recents_tv_gird_row_top_margin"
android:focusable="true" />
-
<View
android:id="@+id/pip_shade"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:visibility="invisible"
+ android:visibility="gone"
android:background="#76000000"/>
<!-- Placeholder view to handle key events for PIP when it's focused.
- Size and positions will be adjusted to comply with
- config_pictureInPictureBoundsInRecents -->
+ Size and positions will be adjusted to comply with the PIP bounds -->
<View
android:id="@+id/pip"
android:layout_width="0dp"
android:layout_height="0dp"
- android:visibility="invisible"
+ android:visibility="gone"
android:focusable="true" />
</com.android.systemui.recents.tv.views.RecentsTvView>
diff --git a/packages/SystemUI/res/layout/recents_tv_task_card_view.xml b/packages/SystemUI/res/layout/recents_tv_task_card_view.xml
index 54e97da..766ef60 100644
--- a/packages/SystemUI/res/layout/recents_tv_task_card_view.xml
+++ b/packages/SystemUI/res/layout/recents_tv_task_card_view.xml
@@ -21,9 +21,11 @@
android:focusableInTouchMode="true"
android:layout_gravity="center"
android:layout_centerInParent="true"
+ android:orientation="vertical"
android:layoutDirection="ltr">
<LinearLayout
+ android:id="@+id/recents_tv_card"
android:layout_width="@dimen/recents_tv_card_width"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
@@ -66,4 +68,30 @@
android:layout_centerHorizontal="true"
android:layout_below="@id/card_title_text" />
</LinearLayout>
+ <LinearLayout
+ android:id="@+id/card_dismiss"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_gravity="center_horizontal"
+ android:layout_below="@id/recents_tv_card"
+ android:alpha="0.0">
+ <ImageView
+ android:id="@+id/card_dismiss_icon"
+ android:layout_width="@dimen/recents_tv_dismiss_icon_size"
+ android:layout_height="@dimen/recents_tv_dismiss_icon_size"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginTop="@dimen/recents_tv_dismiss_icon_top_margin"
+ android:layout_marginBottom="@dimen/recents_tv_dismiss_icon_bottom_margin"
+ android:src="@drawable/ic_cancel_white_24dp" />
+ <TextView
+ android:id="@+id/card_dismiss_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="@dimen/recents_tv_dismiss_text_size"
+ android:fontFamily="@string/font_roboto_light"
+ android:textColor="@color/recents_tv_dismiss_text_color"
+ android:text="@string/recents_tv_dismiss"
+ android:layout_gravity="center_horizontal" />
+ </LinearLayout>
</com.android.systemui.recents.tv.views.TaskCardView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/colors_tv.xml b/packages/SystemUI/res/values/colors_tv.xml
index af99aae..4126d3c 100644
--- a/packages/SystemUI/res/values/colors_tv.xml
+++ b/packages/SystemUI/res/values/colors_tv.xml
@@ -19,4 +19,5 @@
<resources>
<color name="recents_tv_card_background_color">#FF37474F</color>
<color name="recents_tv_card_title_text_color">#CCEEEEEE</color>
+ <color name="recents_tv_dismiss_text_color">#7FEEEEEE</color>
</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 8b433f9..8cd2167 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -397,9 +397,6 @@
<!-- The padding on top of the first notification to the children container -->
<dimen name="notification_children_container_top_padding">8dp</dimen>
- <!-- The vertical distance from which the notification appear when children are expanded -->
- <dimen name="notification_appear_distance">140dp</dimen>
-
<!-- end margin for multi user switch in expanded quick settings -->
<dimen name="multi_user_switch_expanded_margin">8dp</dimen>
diff --git a/packages/SystemUI/res/values/dimens_tv.xml b/packages/SystemUI/res/values/dimens_tv.xml
index 6b153d1..953dd65 100644
--- a/packages/SystemUI/res/values/dimens_tv.xml
+++ b/packages/SystemUI/res/values/dimens_tv.xml
@@ -18,8 +18,8 @@
-->
<resources>
<!-- Dimens for recents card in the recents view on tv -->
- <dimen name="recents_tv_card_width">268dip</dimen>
- <dimen name="recents_tv_screenshot_height">151dip</dimen>
+ <dimen name="recents_tv_card_width">240dip</dimen>
+ <dimen name="recents_tv_screenshot_height">135dip</dimen>
<dimen name="recents_tv_card_extra_badge_size">20dip</dimen>
<dimen name="recents_tv_banner_width">114dip</dimen>
<dimen name="recents_tv_banner_height">64dip</dimen>
@@ -29,10 +29,10 @@
<dimen name="recents_tv_text_padding_bottom">12dip</dimen>
<!-- Padding for grid view in recents view on tv -->
- <dimen name="recents_tv_grid_row_padding">56dip</dimen>
- <dimen name="recents_tv_gird_row_top_padding">57dip</dimen>
+ <dimen name="recents_tv_gird_row_top_margin">215dip</dimen>
<dimen name="recents_tv_grid_max_row_height">268dip</dimen>
- <dimen name="recents_tv_gird_card_spacing">20dip</dimen>
+ <dimen name="recents_tv_gird_card_spacing">8dip</dimen>
+ <dimen name="recents_tv_gird_focused_card_delta">44dip</dimen>
<!-- Values for focus animation -->
<dimen name="recents_tv_unselected_item_z">6dp</dimen>
@@ -43,4 +43,13 @@
<!-- Values for text on recents cards on tv -->
<dimen name="recents_tv_title_text_size">12sp</dimen>
+
+ <!-- Values for card dismiss state -->
+ <dimen name="recents_tv_dismiss_shift_down">48dip</dimen>
+ <dimen name="recents_tv_dismiss_top_margin">356dip</dimen>
+ <dimen name="recents_tv_dismiss_icon_size">24dip</dimen>
+ <dimen name="recents_tv_dismiss_icon_top_margin">38dip</dimen>
+ <dimen name="recents_tv_dismiss_icon_bottom_margin">1dip</dimen>
+ <dimen name="recents_tv_dismiss_text_size">12sp</dimen>
+
</resources>
diff --git a/packages/SystemUI/res/values/integers_tv.xml b/packages/SystemUI/res/values/integers_tv.xml
index bfd8f8b..c60c245 100644
--- a/packages/SystemUI/res/values/integers_tv.xml
+++ b/packages/SystemUI/res/values/integers_tv.xml
@@ -15,4 +15,6 @@
-->
<resources>
<integer name="item_scale_anim_duration">150</integer>
+ <integer name="dismiss_short_duration">200</integer>
+ <integer name="dismiss_long_duration">400</integer>
</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 5295ccb..2bde141 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -570,6 +570,9 @@
<!-- Content description of the clear button in the notification panel for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_clear_all">Clear all notifications.</string>
+ <!-- The overflow indicator shown when a group has more notification inside the group than the visible ones. An example is "+ 3" [CHAR LIMIT=5] -->
+ <string name="notification_group_overflow_indicator">+ <xliff:g id="number" example="3">%s</xliff:g></string>
+
<!-- Content description of button in notification inspector for system settings relating to
notifications from this application [CHAR LIMIT=NONE] -->
<string name="status_bar_notification_inspect_item_title">Notification settings</string>
diff --git a/packages/SystemUI/res/values/strings_tv.xml b/packages/SystemUI/res/values/strings_tv.xml
index 0e1fe8f..52aba0d 100644
--- a/packages/SystemUI/res/values/strings_tv.xml
+++ b/packages/SystemUI/res/values/strings_tv.xml
@@ -19,13 +19,13 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Picture-in-Picture (PIP) menu -->
<eat-comment />
- <!-- Button to close picture-in-picture (PIP) in PIP menu [CHAR LIMIT=16] -->
+ <!-- Button to close picture-in-picture (PIP) in PIP menu [CHAR LIMIT=30] -->
<string name="pip_close">Close PIP</string>
- <!-- Button to move picture-in-picture (PIP) screen to the fullscreen in PIP menu [CHAR LIMIT=16] -->
+ <!-- Button to move picture-in-picture (PIP) screen to the fullscreen in PIP menu [CHAR LIMIT=30] -->
<string name="pip_fullscreen">Full screen</string>
- <!-- Button to play the current media on picture-in-picture (PIP) [CHAR LIMIT=16] -->
+ <!-- Button to play the current media on picture-in-picture (PIP) [CHAR LIMIT=30] -->
<string name="pip_play">Play</string>
- <!-- Button to pause the current media on picture-in-picture (PIP) [CHAR LIMIT=16] -->
+ <!-- Button to pause the current media on picture-in-picture (PIP) [CHAR LIMIT=30] -->
<string name="pip_pause">Pause</string>
<!-- Overlay text on picture-in-picture (PIP) to indicate that longpress HOME key to control PIP [CHAR LIMIT=52] -->
<string name="pip_hold_home">Hold <b>HOME</b> to control PIP</string>
@@ -35,7 +35,11 @@
<string name="pip_onboarding_description">Press and hold the HOME button to control PIP</string>
<!-- Button to close picture-in-picture (PIP) onboarding screen. -->
<string name="pip_onboarding_button">Got it</string>
+ <!-- Dismiss icon description -->
+ <string name="recents_tv_dismiss">Dismiss</string>
<!-- Font for Recents -->
<!-- DO NOT TRANSLATE -->
<string name="font_roboto_regular" translatable="false">sans-serif</string>
+ <!-- DO NOT TRANSLATE -->
+ <string name="font_roboto_light" translatable="false">sans-serif-light</string>
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index b0c1e95..21ad216 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -305,7 +305,7 @@
<style name="TextAppearance.NotificationGuts">
<item name="android:textSize">14sp</item>
- <item name="android:fontFamily">sans-serif-medium</item>
+ <item name="android:fontFamily">roboto-regular</item>
<item name="android:textColor">@android:color/black</item>
</style>
diff --git a/packages/SystemUI/res/values/values_tv.xml b/packages/SystemUI/res/values/values_tv.xml
index 6a72e54..bd72c51 100644
--- a/packages/SystemUI/res/values/values_tv.xml
+++ b/packages/SystemUI/res/values/values_tv.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<item format="float" type="integer" name="unselected_scale">1.0</item>
- <item format="float" type="integer" name="selected_scale">1.1</item>
+ <item format="float" type="integer" name="selected_scale">1.259</item>
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 2b6ed44..da07aec 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -17,6 +17,7 @@
package com.android.systemui.recents;
import android.app.ActivityManager;
+import android.app.UiModeManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -53,6 +54,7 @@
import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.model.RecentsTaskLoader;
+import com.android.systemui.recents.tv.RecentsTvImpl;
import java.util.ArrayList;
@@ -182,7 +184,13 @@
sTaskLoader = new RecentsTaskLoader(mContext);
sConfiguration = new RecentsConfiguration(mContext);
mHandler = new Handler();
- mImpl = new RecentsImpl(mContext);
+ UiModeManager uiModeManager = (UiModeManager) mContext.
+ getSystemService(Context.UI_MODE_SERVICE);
+ if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) {
+ mImpl = new RecentsTvImpl(mContext);
+ } else {
+ mImpl = new RecentsImpl(mContext);
+ }
// Check if there is a recents override package
if ("userdebug".equals(Build.TYPE) || "eng".equals(Build.TYPE)) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 9be24de..880fe10 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -21,12 +21,10 @@
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ITaskStackListener;
-import android.app.UiModeManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -66,7 +64,6 @@
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskGrouping;
import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.tv.views.TaskCardView;
import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
import com.android.systemui.recents.views.TaskStackView;
import com.android.systemui.recents.views.TaskStackViewScroller;
@@ -96,10 +93,6 @@
public final static String RECENTS_PACKAGE = "com.android.systemui";
public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
- public final static String RECENTS_TV_ACTIVITY = "com.android.systemui.recents.tv.RecentsTvActivity";
-
- //Used to store tv or non-tv activty for use in creating intents.
- private final String mRecentsIntentActivityName;
/**
* An implementation of ITaskStackListener, that allows us to listen for changes to the system
@@ -158,16 +151,15 @@
}
}
- private static RecentsTaskLoadPlan sInstanceLoadPlan;
+ protected static RecentsTaskLoadPlan sInstanceLoadPlan;
- Context mContext;
- Handler mHandler;
+ protected Context mContext;
+ protected Handler mHandler;
TaskStackListenerImpl mTaskStackListener;
RecentsAppWidgetHost mAppWidgetHost;
- boolean mCanReuseTaskStackViews = true;
+ protected boolean mCanReuseTaskStackViews = true;
boolean mDraggingInRecents;
boolean mLaunchedWhileDocking;
- private boolean mIsRunningOnTv;
// Task launching
Rect mSearchBarBounds = new Rect();
@@ -182,11 +174,11 @@
// Header (for transition)
TaskViewHeader mHeaderBar;
final Object mHeaderBarLock = new Object();
- TaskStackView mDummyStackView;
+ protected TaskStackView mDummyStackView;
// Variables to keep track of if we need to start recents after binding
- boolean mTriggeredFromAltTab;
- long mLastToggleTime;
+ protected boolean mTriggeredFromAltTab;
+ protected long mLastToggleTime;
DozeTrigger mFastAltTabTrigger = new DozeTrigger(FAST_ALT_TAB_DELAY_MS, new Runnable() {
@Override
public void run() {
@@ -197,7 +189,7 @@
}
});
- Bitmap mThumbnailTransitionBitmapCache;
+ protected Bitmap mThumbnailTransitionBitmapCache;
Task mThumbnailTransitionBitmapCacheKey;
public RecentsImpl(Context context) {
@@ -227,16 +219,6 @@
launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
launchOpts.onlyLoadForCache = true;
loader.loadTasks(mContext, plan, launchOpts);
-
- //Manager used to determine if we are running on tv or not
- UiModeManager uiModeManager = (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE);
- if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) {
- mRecentsIntentActivityName = RECENTS_TV_ACTIVITY;
- mIsRunningOnTv = true;
- } else {
- mRecentsIntentActivityName = RECENTS_ACTIVITY;
- mIsRunningOnTv = false;
- }
}
public void onBootCompleted() {
@@ -729,7 +711,7 @@
/**
* Creates the activity options for a unknown state->recents transition.
*/
- private ActivityOptions getUnknownTransitionActivityOptions() {
+ protected ActivityOptions getUnknownTransitionActivityOptions() {
return ActivityOptions.makeCustomAnimation(mContext,
R.anim.recents_from_unknown_enter,
R.anim.recents_from_unknown_exit,
@@ -739,7 +721,7 @@
/**
* Creates the activity options for a home->recents transition.
*/
- private ActivityOptions getHomeTransitionActivityOptions(boolean fromSearchHome) {
+ protected ActivityOptions getHomeTransitionActivityOptions(boolean fromSearchHome) {
if (fromSearchHome) {
return ActivityOptions.makeCustomAnimation(mContext,
R.anim.recents_from_search_launcher_enter,
@@ -797,22 +779,6 @@
}
}
- /**
- * Creates the activity options for an app->recents transition on TV.
- */
- private ActivityOptions getThumbnailTransitionActivityOptionsForTV(
- ActivityManager.RunningTaskInfo topTask) {
- Bitmap thumbnail = mThumbnailTransitionBitmapCache;
- Rect rect = TaskCardView.getStartingCardThumbnailRect(mContext);
- if (thumbnail != null) {
- return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
- null, (int) rect.left, (int) rect.top,
- (int) rect.width(), (int) rect.height(), mHandler, null);
- }
- // If both the screenshot and thumbnail fails, then just fall back to the default transition
- return getUnknownTransitionActivityOptions();
- }
-
private Bitmap getThumbnailBitmap(ActivityManager.RunningTaskInfo topTask, Task toTask,
TaskViewTransform toTransform) {
Bitmap thumbnail;
@@ -888,15 +854,10 @@
/**
* Shows the recents activity
*/
- private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
+ protected void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
boolean isTopTaskHome, boolean animate) {
RecentsTaskLoader loader = Recents.getTaskLoader();
- // If we are on TV, divert to a different helper method
- if (mIsRunningOnTv) {
- setUpAndStartTvRecents(topTask, isTopTaskHome, animate);
- return;
- }
// In the case where alt-tab is triggered, we never get a preloadRecents() call, so we
// should always preload the tasks now. If we are dragging in recents, reload them as
// the stacks might have changed.
@@ -972,90 +933,6 @@
}
/**
- * Used to set up the animations of Tv Recents, then start the Recents Activity.
- * TODO: Add the Transitions for Home -> Recents TV
- * TODO: Shift Transition code to separate class under /tv directory and access
- * from here
- */
- private void setUpAndStartTvRecents(ActivityManager.RunningTaskInfo topTask,
- boolean isTopTaskHome, boolean animate) {
- RecentsTaskLoader loader = Recents.getTaskLoader();
-
- // In the case where alt-tab is triggered, we never get a preloadRecents() call, so we
- // should always preload the tasks now. If we are dragging in recents, reload them as
- // the stacks might have changed.
- if (mLaunchedWhileDocking || mTriggeredFromAltTab || sInstanceLoadPlan == null) {
- // Create a new load plan if preloadRecents() was never triggered
- sInstanceLoadPlan = loader.createLoadPlan(mContext);
- }
- if (mLaunchedWhileDocking || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
- loader.preloadTasks(sInstanceLoadPlan, topTask.id, isTopTaskHome);
- }
- TaskStack stack = sInstanceLoadPlan.getTaskStack();
-
- // Update the header bar if necessary
- updateHeaderBarLayout(false /* tryAndBindSearchWidget */, stack);
-
- // Prepare the dummy stack for the transition
- TaskStackLayoutAlgorithm.VisibilityReport stackVr =
- mDummyStackView.computeStackVisibilityReport();
-
- if (!animate) {
- ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, -1, -1);
- startRecentsActivity(topTask, opts, false /* fromHome */,
- false /* fromSearchHome */, false /* fromThumbnail*/, stackVr);
- return;
- }
-
- boolean hasRecentTasks = stack.getTaskCount() > 0;
- boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
-
- if (useThumbnailTransition) {
- // Try starting with a thumbnail transition
- ActivityOptions opts = getThumbnailTransitionActivityOptionsForTV(topTask);
- if (opts != null) {
- startRecentsActivity(topTask, opts, false /* fromHome */,
- false /* fromSearchHome */, true /* fromThumbnail */, stackVr);
- } else {
- // Fall through below to the non-thumbnail transition
- useThumbnailTransition = false;
- }
- }
-
- if (!useThumbnailTransition) {
- // If there is no thumbnail transition, but is launching from home into recents, then
- // use a quick home transition and do the animation from home
- if (hasRecentTasks) {
- SystemServicesProxy ssp = Recents.getSystemServices();
- String homeActivityPackage = ssp.getHomeActivityPackageName();
- String searchWidgetPackage = null;
- if (RecentsDebugFlags.Static.EnableSearchBar) {
- searchWidgetPackage = Prefs.getString(mContext,
- Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE, null);
- } else {
- AppWidgetProviderInfo searchWidgetInfo = ssp.resolveSearchAppWidget();
- if (searchWidgetInfo != null) {
- searchWidgetPackage = searchWidgetInfo.provider.getPackageName();
- }
- }
-
- // Determine whether we are coming from a search owned home activity
- boolean fromSearchHome = (homeActivityPackage != null) &&
- homeActivityPackage.equals(searchWidgetPackage);
- ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome);
- startRecentsActivity(topTask, opts, true /* fromHome */, fromSearchHome,
- false /* fromThumbnail */, stackVr);
- } else {
- // Otherwise we do the normal fade from an unknown source
- ActivityOptions opts = getUnknownTransitionActivityOptions();
- startRecentsActivity(topTask, opts, true /* fromHome */,
- false /* fromSearchHome */, false /* fromThumbnail */, stackVr);
- }
- }
- mLastToggleTime = SystemClock.elapsedRealtime();
- }
-
- /**
* Starts the recents activity.
*/
private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
@@ -1078,7 +955,7 @@
launchState.launchedWhileDocking = mLaunchedWhileDocking;
Intent intent = new Intent();
- intent.setClassName(RECENTS_PACKAGE, mRecentsIntentActivityName);
+ intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
| Intent.FLAG_ACTIVITY_TASK_ON_HOME);
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 532e796..330d138 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -47,7 +47,6 @@
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
-import android.hardware.display.DisplayManager;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@@ -73,6 +72,7 @@
import com.android.systemui.R;
import com.android.systemui.recents.RecentsDebugFlags;
import com.android.systemui.recents.RecentsImpl;
+import com.android.systemui.recents.tv.RecentsTvImpl;
import java.io.IOException;
import java.util.ArrayList;
@@ -94,12 +94,18 @@
final static String TAG = "SystemServicesProxy";
final static BitmapFactory.Options sBitmapOptions;
-
static {
sBitmapOptions = new BitmapFactory.Options();
sBitmapOptions.inMutable = true;
}
+ final static List<String> sRecentsBlacklist;
+ static {
+ sRecentsBlacklist = new ArrayList<>();
+ sRecentsBlacklist.add("com.android.systemui.tv.pip.PipOnboardingActivity");
+ sRecentsBlacklist.add("com.android.systemui.tv.pip.PipMenuActivity");
+ }
+
AccessibilityManager mAccm;
ActivityManager mAm;
IActivityManager mIam;
@@ -227,12 +233,13 @@
// Check the first non-recents task, include this task even if it is marked as excluded
// from recents if we are currently in the app. In other words, only remove excluded
- // tasks if it is not the first active task.
+ // tasks if it is not the first active task, and not in the blacklist.
boolean isExcluded = (t.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
== Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
+ boolean isBlackListed = sRecentsBlacklist.contains(t.realActivity.getClassName());
// Filter out recent tasks from managed profiles which are in quiet mode.
isExcluded |= quietProfileIds.contains(t.userId);
- if (isExcluded && (isTopTaskHome || !isFirstValidTask)) {
+ if (isBlackListed || (isExcluded && (isTopTaskHome || !isFirstValidTask))) {
iter.remove();
continue;
}
@@ -280,7 +287,7 @@
// Check if the front most activity is recents
if ((topActivity.getPackageName().equals(RecentsImpl.RECENTS_PACKAGE) &&
(topActivity.getClassName().equals(RecentsImpl.RECENTS_ACTIVITY) ||
- topActivity.getClassName().equals(RecentsImpl.RECENTS_TV_ACTIVITY)))) {
+ topActivity.getClassName().equals(RecentsTvImpl.RECENTS_TV_ACTIVITY)))) {
if (isHomeTopMost != null) {
isHomeTopMost.value = false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
index dae522f..960bd8c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
@@ -85,9 +85,13 @@
private PipManager mPipManager;
private PipManager.Listener mPipListener = new PipManager.Listener() {
@Override
+ public void onPipEntered() {
+ updatePipUI();
+ }
+
+ @Override
public void onPipActivityClosed() {
- mPipView.setVisibility(View.GONE);
- mPipShadeView.setVisibility(View.GONE);
+ updatePipUI();
}
@Override
@@ -102,6 +106,7 @@
@Override
public void onMediaControllerChanged() { }
};
+ private boolean mHasPip;
/**
* A common Runnable to finish Recents by launching Home with an animation depending on the
@@ -266,6 +271,10 @@
homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
mFinishLaunchHomeRunnable = new FinishRecentsRunnable(homeIntent);
+
+ mHasPip = false;
+ updatePipUI();
+ mPipManager.addListener(mPipListener);
}
@Override
@@ -296,34 +305,6 @@
SystemServicesProxy ssp = Recents.getSystemServices();
EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, true));
- if (mPipManager.isPipShown()) {
- // Place mPipView at the PIP bounds for fine tuned focus handling.
- Rect pipBounds = mPipManager.getPipBounds();
- LayoutParams lp = (LayoutParams) mPipView.getLayoutParams();
- lp.width = pipBounds.width();
- lp.height = pipBounds.height();
- lp.leftMargin = pipBounds.left;
- lp.topMargin = pipBounds.top;
- mPipView.setLayoutParams(lp);
-
- mPipView.setVisibility(View.VISIBLE);
- mPipView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- mPipManager.resizePinnedStack(PipManager.STATE_PIP_MENU);
- }
- });
- mPipView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- mPipManager.onPipViewFocusChangedInRecents(hasFocus);
- mPipShadeView.setVisibility(hasFocus ? View.VISIBLE : View.INVISIBLE);
- }
- });
- mPipManager.addListener(mPipListener);
- } else {
- mPipView.setVisibility(View.GONE);
- }
mPipManager.onRecentsStarted();
// Give focus to the recents row whenever its visible to an user.
mRecentsView.requestFocus();
@@ -340,7 +321,6 @@
super.onStop();
mPipManager.onRecentsStopped();
- mPipManager.removeListener(mPipListener);
mIgnoreAltTabRelease = false;
// Notify that recents is now hidden
EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, false));
@@ -357,6 +337,7 @@
protected void onDestroy() {
super.onDestroy();
+ mPipManager.removeListener(mPipListener);
// In the case that the activity finished on startup, just skip the unregistration below
if (mFinishedOnStartup) {
return;
@@ -480,4 +461,40 @@
});
return true;
}
+
+ private void updatePipUI() {
+ if (mHasPip == mPipManager.isPipShown()) {
+ return;
+ }
+ mHasPip = mPipManager.isPipShown();
+ if (mHasPip) {
+ // Place mPipView at the PIP bounds for fine tuned focus handling.
+ Rect pipBounds = mPipManager.getPipBounds();
+ LayoutParams lp = (LayoutParams) mPipView.getLayoutParams();
+ lp.width = pipBounds.width();
+ lp.height = pipBounds.height();
+ lp.leftMargin = pipBounds.left;
+ lp.topMargin = pipBounds.top;
+ mPipView.setLayoutParams(lp);
+
+ mPipView.setVisibility(View.VISIBLE);
+ mPipView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mPipManager.resizePinnedStack(PipManager.STATE_PIP_MENU);
+ }
+ });
+ mPipView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ mPipManager.onPipViewFocusChangedInRecents(hasFocus);
+ mPipShadeView.setVisibility(hasFocus ? View.VISIBLE : View.INVISIBLE);
+ }
+ });
+ mPipShadeView.setVisibility(View.GONE);
+ } else {
+ mPipView.setVisibility(View.GONE);
+ mPipShadeView.setVisibility(View.GONE);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java
new file mode 100644
index 0000000..9fd5d55
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java
@@ -0,0 +1,139 @@
+/*
+ * 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.recents.tv;
+
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import com.android.systemui.recents.*;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.RecentsTaskLoader;
+import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.tv.views.TaskCardView;
+
+public class RecentsTvImpl extends RecentsImpl{
+ public final static String RECENTS_TV_ACTIVITY =
+ "com.android.systemui.recents.tv.RecentsTvActivity";
+
+ public RecentsTvImpl(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
+ boolean isTopTaskHome, boolean animate) {
+ RecentsTaskLoader loader = Recents.getTaskLoader();
+
+ // In the case where alt-tab is triggered, we never get a preloadRecents() call, so we
+ // should always preload the tasks now. If we are dragging in recents, reload them as
+ // the stacks might have changed.
+ if (mTriggeredFromAltTab || sInstanceLoadPlan == null) {
+ // Create a new load plan if preloadRecents() was never triggered
+ sInstanceLoadPlan = loader.createLoadPlan(mContext);
+ }
+ if (mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
+ loader.preloadTasks(sInstanceLoadPlan, topTask.id, isTopTaskHome);
+ }
+ TaskStack stack = sInstanceLoadPlan.getTaskStack();
+
+ if (!animate) {
+ ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, -1, -1);
+ startRecentsActivity(topTask, opts, false /* fromHome */, false /* fromThumbnail*/);
+ return;
+ }
+
+ boolean hasRecentTasks = stack.getTaskCount() > 0;
+ boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
+
+ if (useThumbnailTransition) {
+ // Try starting with a thumbnail transition
+ ActivityOptions opts = getThumbnailTransitionActivityOptionsForTV(topTask);
+ if (opts != null) {
+ startRecentsActivity(topTask, opts, false /* fromHome */, true /* fromThumbnail */);
+ } else {
+ // Fall through below to the non-thumbnail transition
+ useThumbnailTransition = false;
+ }
+ }
+
+ if (!useThumbnailTransition) {
+ // If there is no thumbnail transition, but is launching from home into recents, then
+ // use a quick home transition and do the animation from home
+ if (hasRecentTasks) {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ ActivityOptions opts = getHomeTransitionActivityOptions(false);
+ startRecentsActivity(topTask, opts, true /* fromHome */, false /* fromThumbnail */);
+ } else {
+ // Otherwise we do the normal fade from an unknown source
+ ActivityOptions opts = getUnknownTransitionActivityOptions();
+ startRecentsActivity(topTask, opts, true /* fromHome */, false /* fromThumbnail */);
+ }
+ }
+ mLastToggleTime = SystemClock.elapsedRealtime();
+ }
+
+ protected void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
+ ActivityOptions opts, boolean fromHome, boolean fromThumbnail) {
+ // Update the configuration based on the launch options
+ RecentsConfiguration config = Recents.getConfiguration();
+ RecentsActivityLaunchState launchState = config.getLaunchState();
+ launchState.launchedFromHome = fromHome;
+ launchState.launchedFromSearchHome = false;
+ launchState.launchedFromApp = fromThumbnail;
+ launchState.launchedToTaskId = (topTask != null) ? topTask.id : -1;
+ launchState.launchedWithAltTab = mTriggeredFromAltTab;
+ launchState.launchedReuseTaskStackViews = mCanReuseTaskStackViews;
+ launchState.launchedHasConfigurationChanged = false;
+
+ Intent intent = new Intent();
+ intent.setClassName(RECENTS_PACKAGE, RECENTS_TV_ACTIVITY);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
+ | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
+
+ if (opts != null) {
+ mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
+ } else {
+ mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+ }
+ mCanReuseTaskStackViews = true;
+ EventBus.getDefault().send(new RecentsActivityStartingEvent());
+ }
+
+ /**
+ * Creates the activity options for an app->recents transition on TV.
+ */
+ private ActivityOptions getThumbnailTransitionActivityOptionsForTV(
+ ActivityManager.RunningTaskInfo topTask) {
+ Bitmap thumbnail = mThumbnailTransitionBitmapCache;
+ Rect rect = TaskCardView.getStartingCardThumbnailRect(mContext);
+ if (thumbnail != null) {
+ return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
+ null, (int) rect.left, (int) rect.top,
+ (int) rect.width(), (int) rect.height(), mHandler, null);
+ }
+ // If both the screenshot and thumbnail fails, then just fall back to the default transition
+ return getUnknownTransitionActivityOptions();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/animations/DismissAnimationsHolder.java b/packages/SystemUI/src/com/android/systemui/recents/tv/animations/DismissAnimationsHolder.java
new file mode 100644
index 0000000..8996d0b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/animations/DismissAnimationsHolder.java
@@ -0,0 +1,80 @@
+/*
+ * 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.recents.tv.animations;
+
+
+import android.animation.Animator;
+import android.content.res.Resources;
+import android.support.v4.view.animation.FastOutSlowInInterpolator;
+import android.view.View;
+import android.widget.LinearLayout;
+import com.android.systemui.recents.tv.views.TaskCardView;
+
+import com.android.systemui.R;
+
+public class DismissAnimationsHolder {
+ private LinearLayout mDismissArea;
+ private LinearLayout mTaskCardView;
+ private FastOutSlowInInterpolator mFastOutSlowIn;
+ private int mCardYDelta;
+ private long mShortDuration;
+ private long mLongDuration;
+
+ public DismissAnimationsHolder(TaskCardView taskCardView) {
+ mTaskCardView = (LinearLayout) taskCardView.findViewById(R.id.recents_tv_card);
+ mDismissArea = (LinearLayout) taskCardView.findViewById(R.id.card_dismiss);
+ mFastOutSlowIn = new FastOutSlowInInterpolator();
+
+ Resources res = taskCardView.getResources();
+ mCardYDelta = res.getDimensionPixelOffset(R.dimen.recents_tv_dismiss_shift_down);
+ mShortDuration = res.getInteger(R.integer.dismiss_short_duration);
+ mLongDuration = res.getInteger(R.integer.dismiss_long_duration);
+ }
+
+ public void startEnterAnimation() {
+ mDismissArea.animate().setDuration(mShortDuration);
+ mDismissArea.animate().setInterpolator(mFastOutSlowIn);
+ mDismissArea.animate().alpha(1.0f);
+
+ mTaskCardView.animate().setDuration(mShortDuration);
+ mTaskCardView.animate().setInterpolator(mFastOutSlowIn);
+ mTaskCardView.animate().translationYBy(mCardYDelta);
+ mTaskCardView.animate().alpha(0.5f);
+ }
+
+ public void startExitAnimation() {
+ mDismissArea.animate().setDuration(mShortDuration);
+ mDismissArea.animate().setInterpolator(mFastOutSlowIn);
+ mDismissArea.animate().alpha(0.0f);
+
+ mTaskCardView.animate().setDuration(mShortDuration);
+ mTaskCardView.animate().setInterpolator(mFastOutSlowIn);
+ mTaskCardView.animate().translationYBy(-mCardYDelta);
+ mTaskCardView.animate().alpha(1.0f);
+ }
+
+ public void startDismissAnimation(Animator.AnimatorListener listener) {
+ mDismissArea.animate().setDuration(mShortDuration);
+ mDismissArea.animate().setInterpolator(mFastOutSlowIn);
+ mDismissArea.animate().alpha(0.0f);
+
+ mTaskCardView.animate().setDuration(mLongDuration);
+ mTaskCardView.animate().setInterpolator(mFastOutSlowIn);
+ mTaskCardView.animate().translationYBy(mCardYDelta);
+ mTaskCardView.animate().alpha(0.0f);
+ mTaskCardView.animate().setListener(listener);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/animations/ViewFocusAnimator.java b/packages/SystemUI/src/com/android/systemui/recents/tv/animations/ViewFocusAnimator.java
index 365b29d..888561c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/animations/ViewFocusAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/animations/ViewFocusAnimator.java
@@ -33,6 +33,8 @@
private final float mSelectedScaleDelta;
private final float mUnselectedZ;
private final float mSelectedZDelta;
+ private final float mUnselectedSpacing;
+ private final float mSelectedSpacingDelta;
private final int mAnimDuration;
private final Interpolator mFocusInterpolator;
@@ -57,6 +59,9 @@
mUnselectedZ = res.getDimensionPixelOffset(R.dimen.recents_tv_unselected_item_z);
mSelectedZDelta = res.getDimensionPixelOffset(R.dimen.recents_tv_selected_item_z_delta);
+ mUnselectedSpacing = res.getDimensionPixelOffset(R.dimen.recents_tv_gird_card_spacing);
+ mSelectedSpacingDelta = res.getDimensionPixelOffset(R.dimen.recents_tv_gird_focused_card_delta);
+
mAnimDuration = res.getInteger(R.integer.item_scale_anim_duration);
mFocusInterpolator = new AccelerateDecelerateInterpolator();
@@ -85,10 +90,14 @@
float scale = mUnselectedScale + (level * mSelectedScaleDelta);
float z = mUnselectedZ + (level * mSelectedZDelta);
+ float spacing = mUnselectedSpacing + (level * mSelectedSpacingDelta);
mTargetView.setScaleX(scale);
mTargetView.setScaleY(scale);
mTargetView.setZ(z);
+
+ mTargetView.setPadding((int) spacing, mTargetView.getPaddingTop(),
+ (int) spacing, mTargetView.getPaddingBottom());
}
public float getFocusProgress() {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskCardView.java b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskCardView.java
index 5775b60..3343aec 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskCardView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskCardView.java
@@ -15,6 +15,7 @@
*/
package com.android.systemui.recents.tv.views;
+import android.animation.Animator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
@@ -22,12 +23,14 @@
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Display;
+import android.view.KeyEvent;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.systemui.R;
+import com.android.systemui.recents.tv.animations.DismissAnimationsHolder;
import com.android.systemui.recents.tv.animations.ViewFocusAnimator;
import com.android.systemui.recents.model.Task;
@@ -37,8 +40,10 @@
private TextView mTitleTextView;
private ImageView mBadgeView;
private Task mTask;
+ private boolean mDismissState;
private ViewFocusAnimator mViewFocusAnimator;
+ private DismissAnimationsHolder mDismissAnimationsHolder;
public TaskCardView(Context context) {
this(context, null);
@@ -51,6 +56,7 @@
public TaskCardView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mViewFocusAnimator = new ViewFocusAnimator(this);
+ mDismissState = false;
}
@Override
@@ -58,6 +64,7 @@
mThumbnailView = (ImageView) findViewById(R.id.card_view_thumbnail);
mTitleTextView = (TextView) findViewById(R.id.card_title_text);
mBadgeView = (ImageView) findViewById(R.id.card_extra_badge);
+ mDismissAnimationsHolder = new DismissAnimationsHolder(this);
}
public void init(Task task) {
@@ -98,13 +105,23 @@
int width = res.getDimensionPixelOffset(R.dimen.recents_tv_card_width);
int widthDelta = (int) (width * scale - width);
- int height = (int) (res.getDimensionPixelOffset(
- R.dimen.recents_tv_screenshot_height) * scale);
- int padding = res.getDimensionPixelOffset(R.dimen.recents_tv_grid_row_padding);
+ int height = res.getDimensionPixelOffset(R.dimen.recents_tv_screenshot_height);
+ int heightDelta = (int) (height * scale - height);
+ int topMargin = res.getDimensionPixelOffset(R.dimen.recents_tv_gird_row_top_margin);
- int headerHeight = (int) ((res.getDimensionPixelOffset(
- R.dimen.recents_tv_card_extra_badge_size) +
- res.getDimensionPixelOffset(R.dimen.recents_tv_icon_padding_bottom)) * scale);
+ int headerHeight = res.getDimensionPixelOffset(R.dimen.recents_tv_card_extra_badge_size) +
+ res.getDimensionPixelOffset(R.dimen.recents_tv_icon_padding_bottom);
+ int headerHeightDelta = (int) (headerHeight * scale - headerHeight);
+
+ int dismissAreaHeight =
+ res.getDimensionPixelOffset(R.dimen.recents_tv_dismiss_icon_top_margin) +
+ res.getDimensionPixelOffset(R.dimen.recents_tv_dismiss_icon_bottom_margin) +
+ res.getDimensionPixelOffset(R.dimen.recents_tv_dismiss_icon_size) +
+ res.getDimensionPixelOffset(R.dimen.recents_tv_dismiss_text_size);
+
+ int dismissAreaHeightDelta = (int) (dismissAreaHeight * scale - dismissAreaHeight);
+
+ int totalHeightDelta = heightDelta + headerHeightDelta + dismissAreaHeightDelta;
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
@@ -113,9 +130,72 @@
int screenWidth = size.x;
int screenHeight = size.y;
- return new Rect(screenWidth - width - padding - widthDelta / 2,
- screenHeight / 2 - height / 2 + headerHeight / 2,
- screenWidth - padding + widthDelta / 2,
- screenHeight / 2 + height / 2 + headerHeight / 2);
+ return new Rect(screenWidth / 2 - width / 2 - widthDelta / 2,
+ topMargin - totalHeightDelta / 2 + (int) (headerHeight * scale),
+ screenWidth / 2 + width / 2 + widthDelta / 2,
+ topMargin - totalHeightDelta / 2 + (int) (headerHeight * scale) +
+ (int) (height * scale));
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_DOWN : {
+ if (!isInDismissState()) {
+ setDismissState(true);
+ return true;
+ }
+ break;
+ }
+ case KeyEvent.KEYCODE_DPAD_UP : {
+ if (isInDismissState()) {
+ setDismissState(false);
+ return true;
+ }
+ break;
+ }
+
+ //Eat right and left key presses when we are in dismiss state
+ case KeyEvent.KEYCODE_DPAD_LEFT : {
+ if (isInDismissState()) {
+ return true;
+ }
+ break;
+ }
+ case KeyEvent.KEYCODE_DPAD_RIGHT : {
+ if (isInDismissState()) {
+ return true;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ private void setDismissState(boolean dismissState) {
+ if (mDismissState != dismissState) {
+ mDismissState = dismissState;
+ if (dismissState) {
+ mDismissAnimationsHolder.startEnterAnimation();
+ } else {
+ mDismissAnimationsHolder.startExitAnimation();
+ }
+ }
+ }
+
+ public boolean isInDismissState() {
+ return mDismissState;
+ }
+
+ public void startDismissTaskAnimation(Animator.AnimatorListener listener) {
+ mDismissAnimationsHolder.startDismissAnimation(listener);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ setDismissState(false);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java
index cf8c9bb..5c2de8e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java
@@ -41,7 +41,6 @@
private ArrayList<TaskCardView> mTaskViews = new ArrayList<>();
private Task mFocusedTask;
-
public TaskStackHorizontalGridView(Context context) {
this(context, null);
}
@@ -53,7 +52,7 @@
@Override
protected void onAttachedToWindow() {
EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
- setItemMargin((int) getResources().getDimension(R.dimen.recents_tv_gird_card_spacing));
+ setWindowAlignment(WINDOW_ALIGN_NO_EDGE);
setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
super.onAttachedToWindow();
}
@@ -109,6 +108,13 @@
}
/**
+ * @return - The focused task card view.
+ */
+ public TaskCardView getFocusedTaskCardView() {
+ return ((TaskCardView)findFocus());
+ }
+
+ /**
* @param task
* @return Child view for given task
*/
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalViewAdapter.java b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalViewAdapter.java
index fba424e..3788719 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalViewAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalViewAdapter.java
@@ -15,6 +15,7 @@
*/
package com.android.systemui.recents.tv.views;
+import android.animation.Animator;
import android.app.Activity;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
@@ -25,6 +26,7 @@
import com.android.systemui.R;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.LaunchTvTaskEvent;
+import com.android.systemui.recents.events.ui.DeleteTaskDataEvent;
import com.android.systemui.recents.model.Task;
import java.util.ArrayList;
@@ -39,7 +41,7 @@
private static final String TAG = "TaskStackViewAdapter";
private List<Task> mTaskList;
- static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
+ public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
private TaskCardView mTaskCardView;
private Task mTask;
public ViewHolder(View v) {
@@ -58,9 +60,14 @@
@Override
public void onClick(View v) {
try {
- EventBus.getDefault().send(new LaunchTvTaskEvent(mTaskCardView, mTask,
- null, INVALID_STACK_ID));
- ((Activity)(v.getContext())).finish();
+ if (mTaskCardView.isInDismissState()) {
+ mTaskCardView.startDismissTaskAnimation(
+ getRemoveAtListener(getAdapterPosition(), mTaskCardView));
+ } else {
+ EventBus.getDefault().send(new LaunchTvTaskEvent(mTaskCardView, mTask,
+ null, INVALID_STACK_ID));
+ ((Activity) (v.getContext())).finish();
+ }
} catch (Exception e) {
Log.e(TAG, v.getContext()
.getString(R.string.recents_launch_error_message, mTask.title), e);
@@ -97,4 +104,31 @@
public int getItemCount() {
return mTaskList.size();
}
+
+ private Animator.AnimatorListener getRemoveAtListener(final int position,
+ final TaskCardView taskCardView) {
+ return new Animator.AnimatorListener() {
+
+ @Override
+ public void onAnimationStart(Animator animation) { }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ removeAt(position);
+ EventBus.getDefault().send(new DeleteTaskDataEvent(taskCardView.getTask()));
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) { }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) { }
+ };
+
+ }
+
+ private void removeAt(int position) {
+ mTaskList.remove(position);
+ notifyItemRemoved(position);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 7f61e7a..132c09f 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -83,18 +83,6 @@
private static final int TASK_POSITION_SAME = Integer.MAX_VALUE;
/**
- * Fraction of the divider position between two snap targets to switch to the full-screen
- * target.
- */
- private static final float SWITCH_FULLSCREEN_FRACTION = 0.12f;
-
- /**
- * Fraction of the divider position between two snap targets to switch to the larger target
- * for the bottom/right app layout.
- */
- private static final float BOTTOM_RIGHT_SWITCH_BIGGER_FRACTION = 0.2f;
-
- /**
* How much the background gets scaled when we are in the minimized dock state.
*/
private static final float MINIMIZE_DOCK_SCALE = 0.375f;
@@ -653,12 +641,6 @@
restrictDismissingTaskPosition(taskPosition, mDockSide, taskSnapTarget);
int taskPositionOther =
restrictDismissingTaskPosition(taskPosition, dockSideInverted, taskSnapTarget);
-
- taskPositionDocked = minimizeHoles(position, taskPositionDocked, mDockSide,
- taskSnapTarget);
- taskPositionOther = minimizeHoles(position, taskPositionOther, dockSideInverted,
- taskSnapTarget);
-
calculateBoundsForPosition(taskPositionDocked, mDockSide, mDockedTaskRect);
calculateBoundsForPosition(taskPositionOther, dockSideInverted, mOtherTaskRect);
mDisplayRect.set(0, 0, mDisplayWidth, mDisplayHeight);
@@ -724,51 +706,6 @@
}
/**
- * Given the current split position and the task position calculated by dragging, this
- * method calculates a "better" task position in a sense so holes get smaller while dragging.
- *
- * @return the new task position
- */
- private int minimizeHoles(int position, int taskPosition, int dockSide,
- SnapTarget taskSnapTarget) {
- if (dockSideTopLeft(dockSide)) {
- if (position > taskPosition) {
- SnapTarget nextTarget = mSnapAlgorithm.getNextTarget(taskSnapTarget);
-
- // If the next target is the dismiss end target, switch earlier to make the hole
- // smaller.
- if (nextTarget != taskSnapTarget
- && nextTarget == mSnapAlgorithm.getDismissEndTarget()) {
- float t = (float) (position - taskPosition)
- / (nextTarget.position - taskPosition);
- if (t > SWITCH_FULLSCREEN_FRACTION) {
- return nextTarget.position;
- }
- }
- }
- } else if (dockSideBottomRight(dockSide)) {
- if (position < taskPosition) {
- SnapTarget previousTarget = mSnapAlgorithm.getPreviousTarget(taskSnapTarget);
- if (previousTarget != taskSnapTarget) {
- float t = (float) (taskPosition - position)
- / (taskPosition - previousTarget.position);
-
- // In general, switch a bit earlier (at 20% instead of 50%), but if we are
- // dismissing the top, switch really early.
- float threshold = previousTarget == mSnapAlgorithm.getDismissStartTarget()
- ? SWITCH_FULLSCREEN_FRACTION
- : BOTTOM_RIGHT_SWITCH_BIGGER_FRACTION;
- if (t > threshold) {
- return previousTarget.position;
- }
-
- }
- }
- }
- return taskPosition;
- }
-
- /**
* When the snap target is dismissing one side, make sure that the dismissing side doesn't get
* 0 size.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
index e312fa2..ef32f7e 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
@@ -100,8 +100,8 @@
@Override
public void run() {
try {
- ActivityManagerNative.getDefault().resizeStack(DOCKED_STACK_ID, null, true, true,
- false);
+ ActivityManagerNative.getDefault().resizeStack(
+ DOCKED_STACK_ID, null, true, true, false, -1);
} catch (RemoteException e) {
Log.w(TAG, "Failed to resize stack: " + e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index bb4a771..7119559c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -2056,24 +2056,24 @@
}
for (int i = 0; i < N; i++) {
NotificationData.Entry entry = activeNotifications.get(i);
+ boolean childNotification = mGroupManager.isChildInGroupWithSummary(entry.notification);
if (onKeyguard) {
entry.row.setOnKeyguard(true);
} else {
entry.row.setOnKeyguard(false);
- boolean top = (i == 0);
- entry.row.setSystemExpanded(top);
+ entry.row.setSystemExpanded(visibleNotifications == 0 && !childNotification);
}
- boolean childNotification = mGroupManager.isChildInGroupWithSummary(entry.notification);
+ boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup(entry.notification);
boolean childWithVisibleSummary = childNotification
&& mGroupManager.getGroupSummary(entry.notification).getVisibility()
== View.VISIBLE;
boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification);
- if ((isLockscreenPublicMode() && !mShowLockscreenNotifications) ||
+ if (suppressedSummary || (isLockscreenPublicMode() && !mShowLockscreenNotifications) ||
(onKeyguard && (visibleNotifications >= maxNotifications
&& !childWithVisibleSummary
|| !showOnKeyguard))) {
entry.row.setVisibility(View.GONE);
- if (onKeyguard && showOnKeyguard && !childNotification) {
+ if (onKeyguard && showOnKeyguard && !childNotification && !suppressedSummary) {
mKeyguardIconOverflowContainer.getIconsView().addNotification(entry);
}
} else {
@@ -2082,7 +2082,8 @@
if (!childNotification) {
if (wasGone) {
// notify the scroller of a child addition
- mStackScroller.generateAddAnimation(entry.row, true /* fromMoreCard */);
+ mStackScroller.generateAddAnimation(entry.row,
+ !showOnKeyguard /* fromMoreCard */);
}
visibleNotifications++;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 22bb8eb..2446535 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -43,6 +43,7 @@
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingManager;
+import com.android.systemui.statusbar.notification.HybridNotificationView;
import com.android.systemui.statusbar.notification.NotificationViewWrapper;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -227,6 +228,7 @@
updateClearability();
if (mIsSummaryWithChildren) {
recreateNotificationHeader();
+ mChildrenContainer.onNotificationUpdated();
}
if (mIconAnimationRunning) {
setIconAnimationRunning(true);
@@ -584,6 +586,29 @@
mPublicLayout.closeRemoteInput();
}
+ /**
+ * Set by how much the single line view should be indented.
+ */
+ public void setSingleLineWidthIndention(int indention) {
+ mPrivateLayout.setSingleLineWidthIndention(indention);
+ }
+
+ public int getNotificationColor() {
+ int color = getStatusBarNotification().getNotification().color;
+ if (color == Notification.COLOR_DEFAULT) {
+ return mContext.getColor(com.android.internal.R.color.notification_icon_default_color);
+ }
+ return color;
+ }
+
+ public HybridNotificationView getSingleLineView() {
+ return mPrivateLayout.getSingleLineView();
+ }
+
+ public boolean isOnKeyguard() {
+ return mOnKeyguard;
+ }
+
public interface ExpansionLogger {
public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
}
@@ -677,6 +702,7 @@
public void onInflate(ViewStub stub, View inflated) {
mChildrenContainer = (NotificationChildrenContainer) inflated;
mChildrenContainer.setNotificationParent(ExpandableNotificationRow.this);
+ mChildrenContainer.onNotificationUpdated();
mTranslateableViews.add(mChildrenContainer);
}
});
@@ -1026,11 +1052,8 @@
private void onChildrenCountChanged() {
mIsSummaryWithChildren = BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS
- && mGroupManager.hasGroupChildren(mStatusBarNotification);
+ && mChildrenContainer != null && mChildrenContainer.getChildCount() > 0;
if (mIsSummaryWithChildren) {
- if (mChildrenContainer == null) {
- mChildrenContainerStub.inflate();
- }
if (mNotificationHeader == null) {
recreateNotificationHeader();
}
@@ -1270,7 +1293,7 @@
@Override
public int getMinExpandHeight() {
if (mIsSummaryWithChildren && !mShowingPublic) {
- return mChildrenContainer.getMinExpandHeight(mOnKeyguard);
+ return mChildrenContainer.getMinExpandHeight();
}
return getMinHeight();
}
@@ -1357,7 +1380,7 @@
if (isGroupExpanded()) {
return 1.0f;
} else if (isUserLocked()) {
- return mChildrenContainer.getChildExpandFraction();
+ return mChildrenContainer.getGroupExpandFraction();
}
}
return 0.0f;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index c2df292..0a41e42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -31,7 +31,7 @@
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.HybridNotificationView;
-import com.android.systemui.statusbar.notification.HybridNotificationViewManager;
+import com.android.systemui.statusbar.notification.HybridGroupManager;
import com.android.systemui.statusbar.notification.NotificationCustomViewWrapper;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.NotificationViewWrapper;
@@ -75,7 +75,7 @@
private NotificationViewWrapper mContractedWrapper;
private NotificationViewWrapper mExpandedWrapper;
private NotificationViewWrapper mHeadsUpWrapper;
- private HybridNotificationViewManager mHybridViewManager;
+ private HybridGroupManager mHybridGroupManager;
private int mClipTopAmount;
private int mContentHeight;
private int mUnrestrictedContentHeight;
@@ -116,10 +116,11 @@
private ExpandableNotificationRow mContainingNotification;
private int mTransformationStartVisibleType;
private boolean mUserExpanding;
+ private int mSingleLineWidthIndention;
public NotificationContentView(Context context, AttributeSet attrs) {
super(context, attrs);
- mHybridViewManager = new HybridNotificationViewManager(getContext(), this);
+ mHybridGroupManager = new HybridGroupManager(getContext(), this);
mMinContractedHeight = getResources().getDimensionPixelSize(
R.dimen.min_notification_layout_height);
mNotificationContentMarginEnd = getResources().getDimensionPixelSize(
@@ -139,6 +140,7 @@
boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY;
boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST;
int maxSize = Integer.MAX_VALUE;
+ int width = MeasureSpec.getSize(widthMeasureSpec);
if (hasFixedHeight || isHeightLimited) {
maxSize = MeasureSpec.getSize(heightMeasureSpec);
}
@@ -187,12 +189,18 @@
maxChildHeight = Math.max(maxChildHeight, mHeadsUpChild.getMeasuredHeight());
}
if (mSingleLineView != null) {
- mSingleLineView.measure(widthMeasureSpec,
+ int singleLineWidthSpec = widthMeasureSpec;
+ if (mSingleLineWidthIndention != 0
+ && MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED) {
+ singleLineWidthSpec = MeasureSpec.makeMeasureSpec(
+ width - mSingleLineWidthIndention + mSingleLineView.getPaddingEnd(),
+ MeasureSpec.AT_MOST);
+ }
+ mSingleLineView.measure(singleLineWidthSpec,
MeasureSpec.makeMeasureSpec(maxSize, MeasureSpec.AT_MOST));
maxChildHeight = Math.max(maxChildHeight, mSingleLineView.getMeasuredHeight());
}
int ownHeight = Math.min(maxChildHeight, maxSize);
- int width = MeasureSpec.getSize(widthMeasureSpec);
setMeasuredDimension(width, ownHeight);
}
@@ -715,7 +723,7 @@
private void updateSingleLineView() {
if (mIsChildInGroup) {
- mSingleLineView = mHybridViewManager.bindFromNotification(
+ mSingleLineView = mHybridGroupManager.bindFromNotification(
mSingleLineView, mStatusBarNotification.getNotification());
} else if (mSingleLineView != null) {
removeView(mSingleLineView);
@@ -878,4 +886,20 @@
updateBackgroundColor(false);
}
}
+
+ /**
+ * Set by how much the single line view should be indented. Used when a overflow indicator is
+ * present and only during measuring
+ */
+ public void setSingleLineWidthIndention(int singleLineWidthIndention) {
+ if (singleLineWidthIndention != mSingleLineWidthIndention) {
+ mSingleLineWidthIndention = singleLineWidthIndention;
+ mContainingNotification.forceLayout();
+ forceLayout();
+ }
+ }
+
+ public HybridNotificationView getSingleLineView() {
+ return mSingleLineView;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridGroupManager.java
similarity index 64%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationViewManager.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridGroupManager.java
index 28bb66f..8f2c81f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridGroupManager.java
@@ -26,6 +26,7 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.statusbar.ExpandableNotificationRow;
@@ -33,18 +34,18 @@
import java.util.List;
/**
- * A class managing {@link HybridNotificationView} views
+ * A class managing hybrid groups that include {@link HybridNotificationView} and the notification
+ * group overflow.
*/
-public class HybridNotificationViewManager {
+public class HybridGroupManager {
private final Context mContext;
private ViewGroup mParent;
- private String mDivider;
+ private int mOverflowNumberColor;
- public HybridNotificationViewManager(Context ctx, ViewGroup parent) {
+ public HybridGroupManager(Context ctx, ViewGroup parent) {
mContext = ctx;
mParent = parent;
- mDivider = " • ";
}
private HybridNotificationView inflateHybridView() {
@@ -55,6 +56,26 @@
return hybrid;
}
+ private TextView inflateOverflowNumber() {
+ LayoutInflater inflater = mContext.getSystemService(LayoutInflater.class);
+ TextView numberView = (TextView) inflater.inflate(
+ R.layout.hybrid_overflow_number, mParent, false);
+ mParent.addView(numberView);
+ updateOverFlowNumberColor(numberView);
+ return numberView;
+ }
+
+ private void updateOverFlowNumberColor(TextView numberView) {
+ numberView.setTextColor(mOverflowNumberColor);
+ }
+
+ public void setOverflowNumberColor(TextView numberView, int overflowNumberColor) {
+ mOverflowNumberColor = overflowNumberColor;
+ if (numberView != null) {
+ updateOverFlowNumberColor(numberView);
+ }
+ }
+
public HybridNotificationView bindFromNotification(HybridNotificationView reusableView,
Notification notification) {
if (reusableView == null) {
@@ -82,33 +103,15 @@
return titleText;
}
- public HybridNotificationView bindFromNotificationGroup(
- HybridNotificationView reusableView,
- List<ExpandableNotificationRow> group, int startIndex) {
+ public TextView bindOverflowNumber(TextView reusableView, int number) {
if (reusableView == null) {
- reusableView = inflateHybridView();
+ reusableView = inflateOverflowNumber();
}
- SpannableStringBuilder summary = new SpannableStringBuilder();
- int childCount = group.size();
- for (int i = startIndex; i < childCount; i++) {
- ExpandableNotificationRow child = group.get(i);
- CharSequence titleText = resolveTitle(
- child.getStatusBarNotification().getNotification());
- if (titleText == null) {
- continue;
- }
- if (!TextUtils.isEmpty(summary)) {
- summary.append(mDivider,
- new TextAppearanceSpan(mContext, R.style.
- TextAppearance_Material_Notification_HybridNotificationDivider),
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- summary.append(BidiFormatter.getInstance().unicodeWrap(titleText));
+ String text = mContext.getResources().getString(
+ R.string.notification_group_overflow_indicator, number);
+ if (!text.equals(reusableView.getText())) {
+ reusableView.setText(text);
}
- // We want to force the same orientation as the layout RTL mode
- BidiFormatter formater = BidiFormatter.getInstance(
- reusableView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
- reusableView.bind(formater.unicodeWrap(summary));
return reusableView;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java
index c80cad8..0a1795f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java
@@ -60,6 +60,14 @@
super(context, attrs, defStyleAttr, defStyleRes);
}
+ public TextView getTitleView() {
+ return mTitleView;
+ }
+
+ public TextView getTextView() {
+ return mTextView;
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
index 6ef61ec..844a2c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
@@ -17,15 +17,19 @@
package com.android.systemui.statusbar.notification;
import android.graphics.Color;
+import android.view.View;
import android.widget.ImageView;
import com.android.internal.util.NotificationColorUtil;
import com.android.systemui.R;
+import com.android.systemui.statusbar.stack.NotificationChildrenContainer;
/**
* A util class for various reusable functions
*/
public class NotificationUtils {
+ private static final int[] sLocationBase = new int[2];
+ private static final int[] sLocationOffset = new int[2];
public static boolean isGrayscale(ImageView v, NotificationColorUtil colorUtil) {
Object isGrayscale = v.getTag(R.id.icon_is_grayscale);
if (isGrayscale != null) {
@@ -47,4 +51,10 @@
(int) interpolate(Color.green(startColor), Color.green(endColor), amount),
(int) interpolate(Color.blue(startColor), Color.blue(endColor), amount));
}
+
+ public static float getRelativeYOffset(View offsetView, View baseView) {
+ baseView.getLocationOnScreen(sLocationBase);
+ offsetView.getLocationOnScreen(sLocationOffset);
+ return sLocationOffset[1] - sLocationBase[1];
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index d3681b7..f7a6b271 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.phone;
import android.service.notification.StatusBarNotification;
-import android.util.ArraySet;
+import android.support.annotation.Nullable;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.NotificationData;
@@ -35,7 +35,7 @@
private final HashMap<String, NotificationGroup> mGroupMap = new HashMap<>();
private OnGroupChangeListener mListener;
private int mBarState = -1;
- private ArraySet<String> mHeadsUpedEntries = new ArraySet<>();
+ private HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>();
public void setOnGroupChangeListener(OnGroupChangeListener listener) {
mListener = listener;
@@ -91,6 +91,7 @@
} else {
group.summary = null;
}
+ updateSuppression(group);
if (group.children.isEmpty()) {
if (group.summary == null) {
mGroupMap.remove(groupKey);
@@ -109,43 +110,81 @@
}
if (isGroupChild) {
group.children.add(added);
+ updateSuppression(group);
} else {
group.summary = added;
group.expanded = added.row.areChildrenExpanded();
+ updateSuppression(group);
if (!group.children.isEmpty()) {
mListener.onGroupCreatedFromChildren(group);
}
}
}
+ private void updateSuppression(NotificationGroup group) {
+ if (group == null) {
+ return;
+ }
+ boolean prevSuppressed = group.suppressed;
+ group.suppressed = group.summary != null && !group.expanded
+ && (group.children.size() == 1
+ || (group.children.size() == 0
+ && !group.summary.notification.getNotification().isGroupChild()
+ && hasIsolatedChildren(group)));
+ if (prevSuppressed != group.suppressed) {
+ mListener.onGroupsChanged();
+ }
+ }
+
+ private boolean hasIsolatedChildren(NotificationGroup group) {
+ return getNumberOfIsolatedChildren(group.summary.notification.getGroupKey()) != 0;
+ }
+
+ private int getNumberOfIsolatedChildren(String groupKey) {
+ int count = 0;
+ for (StatusBarNotification sbn : mIsolatedEntries.values()) {
+ if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn)) {
+ count++;
+ }
+ }
+ return count;
+ }
+
public void onEntryUpdated(NotificationData.Entry entry,
StatusBarNotification oldNotification) {
if (mGroupMap.get(getGroupKey(oldNotification)) != null) {
onEntryRemovedInternal(entry, oldNotification);
}
onEntryAdded(entry);
+ if (isIsolated(entry.notification)) {
+ mIsolatedEntries.put(entry.key, entry.notification);
+ String oldKey = oldNotification.getGroupKey();
+ String newKey = entry.notification.getGroupKey();
+ if (!oldKey.equals(newKey)) {
+ updateSuppression(mGroupMap.get(oldKey));
+ updateSuppression(mGroupMap.get(newKey));
+ }
+ }
}
- public boolean isVisible(StatusBarNotification sbn) {
- if (!isGroupChild(sbn)) {
- return true;
- }
- NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
- if (group != null && (group.expanded || group.summary == null)) {
- return true;
- }
- return false;
+ public boolean isSummaryOfSuppressedGroup(StatusBarNotification sbn) {
+ return isGroupSuppressed(getGroupKey(sbn)) && sbn.getNotification().isGroupSummary();
}
- public boolean hasGroupChildren(StatusBarNotification sbn) {
- if (!isGroupSummary(sbn)) {
- return false;
- }
- NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
- if (group == null) {
- return false;
- }
- return !group.children.isEmpty();
+ public boolean isOnlyChildInSuppressedGroup(StatusBarNotification sbn) {
+ return isGroupSuppressed(sbn.getGroupKey())
+ && sbn.getNotification().isGroupChild()
+ && getTotalNumberOfChildren(sbn) == 1;
+ }
+
+ private int getTotalNumberOfChildren(StatusBarNotification sbn) {
+ return getNumberOfIsolatedChildren(sbn.getGroupKey())
+ + mGroupMap.get(sbn.getGroupKey()).children.size();
+ }
+
+ private boolean isGroupSuppressed(String groupKey) {
+ NotificationGroup group = mGroupMap.get(groupKey);
+ return group != null && group.suppressed;
}
public void setStatusBarState(int newState) {
@@ -163,6 +202,7 @@
if (group.expanded) {
setGroupExpanded(group, false);
}
+ updateSuppression(group);
}
}
@@ -174,7 +214,7 @@
return false;
}
NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
- if (group == null || group.summary == null) {
+ if (group == null || group.summary == null || group.suppressed) {
return false;
}
return true;
@@ -194,11 +234,30 @@
return !group.children.isEmpty();
}
+ /**
+ * Get the summary of a specified status bar notification. For isolated notification this return
+ * itself.
+ */
public ExpandableNotificationRow getGroupSummary(StatusBarNotification sbn) {
- NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
+ return getGroupSummary(getGroupKey(sbn));
+ }
+
+ /**
+ * Similar to {@link #getGroupSummary(StatusBarNotification)} but doesn't get the visual summary
+ * but the logical summary, i.e when a child is isolated, it still returns the summary as if
+ * it wasn't isolated.
+ */
+ public ExpandableNotificationRow getLogicalGroupSummary(
+ StatusBarNotification sbn) {
+ return getGroupSummary(sbn.getGroupKey());
+ }
+
+ @Nullable
+ private ExpandableNotificationRow getGroupSummary(String groupKey) {
+ NotificationGroup group = mGroupMap.get(groupKey);
return group == null ? null
: group.summary == null ? null
- : group.summary.row;
+ : group.summary.row;
}
public void toggleGroupExpansion(StatusBarNotification sbn) {
@@ -210,7 +269,7 @@
}
private boolean isIsolated(StatusBarNotification sbn) {
- return mHeadsUpedEntries.contains(sbn.getKey()) && sbn.getNotification().isGroupChild();
+ return mIsolatedEntries.containsKey(sbn.getKey());
}
private boolean isGroupSummary(StatusBarNotification sbn) {
@@ -249,38 +308,55 @@
public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
final StatusBarNotification sbn = entry.notification;
if (entry.row.isHeadsUp()) {
- if (!mHeadsUpedEntries.contains(sbn.getKey())) {
- final boolean groupChild = sbn.getNotification().isGroupChild();
- if (groupChild) {
- // We will be isolated now, so lets update the groups
- onEntryRemovedInternal(entry, entry.notification);
- }
- mHeadsUpedEntries.add(sbn.getKey());
- if (groupChild) {
- onEntryAdded(entry);
- mListener.onChildIsolationChanged();
- }
+ if (shouldIsolate(sbn)) {
+ // We will be isolated now, so lets update the groups
+ onEntryRemovedInternal(entry, entry.notification);
+
+ mIsolatedEntries.put(sbn.getKey(), sbn);
+
+ onEntryAdded(entry);
+ // We also need to update the suppression of the old group, because this call comes
+ // even before the groupManager knows about the notification at all.
+ // When the notification gets added afterwards it is already isolated and therefore
+ // it doesn't lead to an update.
+ updateSuppression(mGroupMap.get(entry.notification.getGroupKey()));
+ mListener.onGroupsChanged();
}
} else {
- if (mHeadsUpedEntries.contains(sbn.getKey())) {
- boolean isolatedBefore = isIsolated(sbn);
- if (isolatedBefore) {
- // not isolated anymore, we need to update the groups
- onEntryRemovedInternal(entry, entry.notification);
- }
- mHeadsUpedEntries.remove(sbn.getKey());
- if (isolatedBefore) {
- onEntryAdded(entry);
- mListener.onChildIsolationChanged();
- }
+ if (mIsolatedEntries.containsKey(sbn.getKey())) {
+ // not isolated anymore, we need to update the groups
+ onEntryRemovedInternal(entry, entry.notification);
+ mIsolatedEntries.remove(sbn.getKey());
+ onEntryAdded(entry);
+ mListener.onGroupsChanged();
}
}
}
+ private boolean shouldIsolate(StatusBarNotification sbn) {
+ NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey());
+ return sbn.getNotification().isGroupChild()
+ && (sbn.getNotification().fullScreenIntent != null
+ || notificationGroup == null
+ || !notificationGroup.expanded
+ || isGroupNotFullyVisible(notificationGroup));
+ }
+
+ private boolean isGroupNotFullyVisible(NotificationGroup notificationGroup) {
+ return notificationGroup.summary == null
+ || notificationGroup.summary.row.getClipTopOptimization() > 0
+ || notificationGroup.summary.row.getClipTopAmount() > 0
+ || notificationGroup.summary.row.getTranslationY() < 0;
+ }
+
public static class NotificationGroup {
public final HashSet<NotificationData.Entry> children = new HashSet<>();
public NotificationData.Entry summary;
public boolean expanded;
+ /**
+ * Is this notification group suppressed, i.e its summary is hidden
+ */
+ public boolean suppressed;
}
public interface OnGroupChangeListener {
@@ -301,8 +377,9 @@
void onGroupCreatedFromChildren(NotificationGroup group);
/**
- * The isolation of a child has changed i.e it's group changes.
+ * The groups have changed. This can happen if the isolation of a child has changes or if a
+ * group became suppressed / unsuppressed
*/
- void onChildIsolationChanged();
+ void onGroupsChanged();
}
}
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 6e345f0..a605a81 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -114,6 +114,9 @@
if (!PhoneStatusBar.isTopLevelChild(ent)) {
continue;
}
+ if (ent.row.getVisibility() == View.GONE) {
+ continue;
+ }
toShow.add(ent.icon);
}
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 bf58611..e84d8fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -132,6 +132,7 @@
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.GestureRecorder;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.NotificationData;
@@ -1632,16 +1633,14 @@
private void updateSpeedbump() {
int speedbumpIndex = -1;
int currentIndex = 0;
- ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
- final int N = activeNotifications.size();
+ final int N = mStackScroller.getChildCount();
for (int i = 0; i < N; i++) {
- Entry entry = activeNotifications.get(i);
- boolean isChild = !isTopLevelChild(entry);
- if (isChild) {
+ View view = mStackScroller.getChildAt(i);
+ if (view.getVisibility() == View.GONE || !(view instanceof ExpandableNotificationRow)) {
continue;
}
- if (entry.row.getVisibility() != View.GONE &&
- mNotificationData.isAmbient(entry.key)) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ if (mNotificationData.isAmbient(row.getStatusBarNotification().getKey())) {
speedbumpIndex = currentIndex;
break;
}
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 676ff2e..dc567fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -22,13 +22,14 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.ViewInvertHelper;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.HybridGroupManager;
import com.android.systemui.statusbar.notification.HybridNotificationView;
-import com.android.systemui.statusbar.notification.HybridNotificationViewManager;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.phone.NotificationPanelView;
@@ -46,23 +47,22 @@
private final List<View> mDividers = new ArrayList<>();
private final List<ExpandableNotificationRow> mChildren = new ArrayList<>();
- private final HybridNotificationViewManager mHybridViewManager;
+ private final HybridGroupManager mHybridGroupManager;
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;
- private HybridNotificationView mGroupOverflowContainer;
+ private TextView mOverflowNumber;
private ViewState mGroupOverFlowState;
private int mRealHeight;
- private int mLayoutDirection = LAYOUT_DIRECTION_UNDEFINED;
private boolean mUserLocked;
private int mActualHeight;
+ private boolean mNeverAppliedGroupState;
public NotificationChildrenContainer(Context context) {
this(context, null);
@@ -80,7 +80,7 @@
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initDimens();
- mHybridViewManager = new HybridNotificationViewManager(getContext(), this);
+ mHybridGroupManager = new HybridGroupManager(getContext(), this);
}
private void initDimens() {
@@ -90,8 +90,6 @@
R.dimen.notification_divider_height));
mMaxNotificationHeight = getResources().getDimensionPixelSize(
R.dimen.notification_max_height);
- mNotificationAppearDistance = getResources().getDimensionPixelSize(
- R.dimen.notification_appear_distance);
mNotificationHeaderHeight = getResources().getDimensionPixelSize(
com.android.internal.R.dimen.notification_content_margin_top);
mNotificatonTopPadding = getResources().getDimensionPixelSize(
@@ -108,12 +106,12 @@
if (child.getVisibility() == View.GONE) {
continue;
}
- child.layout(0, 0, getWidth(), child.getMeasuredHeight());
+ child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
mDividers.get(i).layout(0, 0, getWidth(), mDividerHeight);
}
- if (mGroupOverflowContainer != null) {
- mGroupOverflowContainer.layout(0, 0, getWidth(),
- mGroupOverflowContainer.getMeasuredHeight());
+ if (mOverflowNumber != null) {
+ mOverflowNumber.layout(getWidth() - mOverflowNumber.getMeasuredWidth(), 0, getWidth(),
+ mOverflowNumber.getMeasuredHeight());
}
}
@@ -128,11 +126,20 @@
ownMaxHeight = Math.min(ownMaxHeight, size);
}
int newHeightSpec = MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.AT_MOST);
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ if (mOverflowNumber != null) {
+ mOverflowNumber.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
+ newHeightSpec);
+ }
int dividerHeightSpec = MeasureSpec.makeMeasureSpec(mDividerHeight, MeasureSpec.EXACTLY);
int height = mNotificationHeaderHeight + mNotificatonTopPadding;
int childCount = Math.min(mChildren.size(), NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED);
+ int collapsedChildren = getMaxAllowedVisibleChildren(true /* likeCollapsed */);
+ int overflowIndex = childCount > collapsedChildren ? collapsedChildren - 1 : -1;
for (int i = 0; i < childCount; i++) {
- View child = mChildren.get(i);
+ ExpandableNotificationRow child = mChildren.get(i);
+ boolean isOverflow = i == overflowIndex;
+ child.setSingleLineWidthIndention(isOverflow ? mOverflowNumber.getMeasuredWidth() : 0);
child.measure(widthMeasureSpec, newHeightSpec);
height += child.getMeasuredHeight();
@@ -141,10 +148,6 @@
divider.measure(widthMeasureSpec, dividerHeightSpec);
height += mDividerHeight;
}
- int width = MeasureSpec.getSize(widthMeasureSpec);
- if (mGroupOverflowContainer != null) {
- mGroupOverflowContainer.measure(widthMeasureSpec, newHeightSpec);
- }
mRealHeight = height;
if (heightMode != MeasureSpec.UNSPECIFIED) {
height = Math.min(height, size);
@@ -200,22 +203,30 @@
public void updateGroupOverflow() {
int childCount = mChildren.size();
int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* likeCollapsed */);
- boolean hasOverflow = childCount > maxAllowedVisibleChildren;
- int lastVisibleIndex = hasOverflow ? maxAllowedVisibleChildren - 2
- : maxAllowedVisibleChildren - 1;
- if (hasOverflow) {
- mGroupOverflowContainer = mHybridViewManager.bindFromNotificationGroup(
- mGroupOverflowContainer, mChildren, lastVisibleIndex + 1);
+ if (childCount > maxAllowedVisibleChildren) {
+ mOverflowNumber = mHybridGroupManager.bindOverflowNumber(
+ mOverflowNumber, childCount - maxAllowedVisibleChildren);
if (mOverflowInvertHelper == null) {
- mOverflowInvertHelper= new ViewInvertHelper(mGroupOverflowContainer,
+ mOverflowInvertHelper= new ViewInvertHelper(mOverflowNumber,
NotificationPanelView.DOZE_ANIMATION_DURATION);
}
if (mGroupOverFlowState == null) {
mGroupOverFlowState = new ViewState();
+ mNeverAppliedGroupState = true;
}
- } else if (mGroupOverflowContainer != null) {
- removeView(mGroupOverflowContainer);
- mGroupOverflowContainer = null;
+ } else if (mOverflowNumber != null) {
+ removeView(mOverflowNumber);
+ if (isShown()) {
+ final View removedOverflowNumber = mOverflowNumber;
+ addTransientView(removedOverflowNumber, getTransientViewCount());
+ CrossFadeHelper.fadeOut(removedOverflowNumber, new Runnable() {
+ @Override
+ public void run() {
+ removeTransientView(removedOverflowNumber);
+ }
+ });
+ }
+ mOverflowNumber = null;
mOverflowInvertHelper = null;
mGroupOverFlowState = null;
}
@@ -224,11 +235,7 @@
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- int layoutDirection = getLayoutDirection();
- if (layoutDirection != mLayoutDirection) {
- updateGroupOverflow();
- mLayoutDirection = layoutDirection;
- }
+ updateGroupOverflow();
}
private View inflateDivider() {
@@ -296,7 +303,7 @@
boolean firstChild = true;
float expandFactor = 0;
if (mUserLocked) {
- expandFactor = getChildExpandFraction();
+ expandFactor = getGroupExpandFraction();
}
for (int i = 0; i < childCount; i++) {
if (visibleChildren >= maxAllowedVisibleChildren) {
@@ -346,14 +353,12 @@
int yPosition = mNotificationHeaderHeight;
boolean firstChild = true;
int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren();
- boolean hasOverflow = !mChildrenExpanded && childCount > maxAllowedVisibleChildren
- && maxAllowedVisibleChildren != NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED;
- int lastVisibleIndex = hasOverflow
- ? maxAllowedVisibleChildren - 2
- : maxAllowedVisibleChildren - 1;
+ int lastVisibleIndex = maxAllowedVisibleChildren - 1;
+ int firstOverflowIndex = lastVisibleIndex + 1;
float expandFactor = 0;
if (mUserLocked) {
- expandFactor = getChildExpandFraction();
+ expandFactor = getGroupExpandFraction();
+ firstOverflowIndex = getMaxAllowedVisibleChildren(true /* likeCollapsed */);
}
for (int i = 0; i < childCount; i++) {
ExpandableNotificationRow child = mChildren.get(i);
@@ -386,19 +391,38 @@
childState.belowSpeedBump = parentState.belowSpeedBump;
childState.clipTopAmount = 0;
childState.topOverLap = 0;
- boolean visible = i <= lastVisibleIndex;
- childState.alpha = visible ? 1 : 0;
+ childState.alpha = 0;
+ if (i < firstOverflowIndex) {
+ childState.alpha = 1;
+ } else if (expandFactor == 1.0f && i <= lastVisibleIndex) {
+ childState.alpha = (mActualHeight - childState.yTranslation) / childState.height;
+ childState.alpha = Math.max(0.0f, Math.min(1.0f, childState.alpha));
+ }
childState.location = parentState.location;
yPosition += intrinsicHeight;
}
- if (mGroupOverflowContainer != null) {
- mGroupOverFlowState.initFrom(mGroupOverflowContainer);
- if (hasOverflow) {
- StackViewState firstOverflowState =
- resultState.getViewStateForView(mChildren.get(lastVisibleIndex + 1));
- mGroupOverFlowState.yTranslation = firstOverflowState.yTranslation;
+ if (mOverflowNumber != null) {
+ ExpandableNotificationRow overflowView = mChildren.get(Math.min(
+ getMaxAllowedVisibleChildren(true /* likeCollpased */), childCount) - 1);
+ mGroupOverFlowState.copyFrom(resultState.getViewStateForView(overflowView));
+ if (!mChildrenExpanded) {
+ if (mUserLocked) {
+ HybridNotificationView singleLineView = overflowView.getSingleLineView();
+ View mirrorView = singleLineView.getTextView();
+ if (mirrorView.getVisibility() == GONE) {
+ mirrorView = singleLineView.getTitleView();
+ }
+ if (mirrorView.getVisibility() == GONE) {
+ mirrorView = singleLineView;
+ }
+ mGroupOverFlowState.yTranslation += NotificationUtils.getRelativeYOffset(
+ mirrorView, overflowView);
+ mGroupOverFlowState.alpha = mirrorView.getAlpha();
+ }
+ } else {
+ mGroupOverFlowState.yTranslation += mNotificationHeaderHeight;
+ mGroupOverFlowState.alpha = 0.0f;
}
- mGroupOverFlowState.alpha = mChildrenExpanded || !hasOverflow ? 0.0f : 1.0f;
}
}
@@ -410,7 +434,8 @@
if (!likeCollapsed && (mChildrenExpanded || mNotificationParent.isUserLocked())) {
return NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED;
}
- if (mNotificationParent.isExpanded() || mNotificationParent.isHeadsUp()) {
+ if (!mNotificationParent.isOnKeyguard()
+ && (mNotificationParent.isExpanded() || mNotificationParent.isHeadsUp())) {
return NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED;
}
return NUMBER_OF_CHILDREN_WHEN_COLLAPSED;
@@ -419,7 +444,10 @@
public void applyState(StackScrollState state) {
int childCount = mChildren.size();
ViewState tmpState = new ViewState();
- float expandFraction = getChildExpandFraction();
+ float expandFraction = 0.0f;
+ if (mUserLocked) {
+ expandFraction = getGroupExpandFraction();
+ }
for (int i = 0; i < childCount; i++) {
ExpandableNotificationRow child = mChildren.get(i);
StackViewState viewState = state.getViewStateForView(child);
@@ -431,13 +459,17 @@
tmpState.yTranslation = viewState.yTranslation - mDividerHeight;
float alpha = mChildrenExpanded && viewState.alpha != 0 ? 0.5f : 0;
if (mUserLocked && viewState.alpha != 0) {
- alpha = NotificationUtils.interpolate(0, 0.5f, expandFraction);
+ alpha = NotificationUtils.interpolate(0, 0.5f,
+ Math.min(viewState.alpha, expandFraction));
}
tmpState.alpha = alpha;
state.applyViewState(divider, tmpState);
+ // There is no fake shadow to be drawn on the children
+ child.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
}
- if (mGroupOverflowContainer != null) {
- state.applyViewState(mGroupOverflowContainer, mGroupOverFlowState);
+ if (mOverflowNumber != null) {
+ state.applyViewState(mOverflowNumber, mGroupOverFlowState);
+ mNeverAppliedGroupState = false;
}
}
@@ -456,7 +488,7 @@
long baseDelay, long duration) {
int childCount = mChildren.size();
ViewState tmpState = new ViewState();
- float expandFraction = getChildExpandFraction();
+ float expandFraction = getGroupExpandFraction();
for (int i = childCount - 1; i >= 0; i--) {
ExpandableNotificationRow child = mChildren.get(i);
StackViewState viewState = state.getViewStateForView(child);
@@ -468,13 +500,23 @@
tmpState.yTranslation = viewState.yTranslation - mDividerHeight;
float alpha = mChildrenExpanded && viewState.alpha != 0 ? 0.5f : 0;
if (mUserLocked && viewState.alpha != 0) {
- alpha = NotificationUtils.interpolate(0, 0.5f, expandFraction);
+ alpha = NotificationUtils.interpolate(0, 0.5f,
+ Math.min(viewState.alpha, expandFraction));
}
tmpState.alpha = alpha;
stateAnimator.startViewAnimations(divider, tmpState, baseDelay, duration);
+ // There is no fake shadow to be drawn on the children
+ child.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
}
- if (mGroupOverflowContainer != null) {
- stateAnimator.startViewAnimations(mGroupOverflowContainer, mGroupOverFlowState,
+ if (mOverflowNumber != null) {
+ if (mNeverAppliedGroupState) {
+ float alpha = mGroupOverFlowState.alpha;
+ mGroupOverFlowState.alpha = 0;
+ state.applyViewState(mOverflowNumber, mGroupOverFlowState);
+ mGroupOverFlowState.alpha = alpha;
+ mNeverAppliedGroupState = false;
+ }
+ stateAnimator.startViewAnimations(mOverflowNumber, mGroupOverFlowState,
baseDelay, duration);
}
}
@@ -529,44 +571,49 @@
return;
}
mActualHeight = actualHeight;
- float fraction = getChildExpandFraction();
+ float fraction = getGroupExpandFraction();
+ int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* forceCollapsed */);
int childCount = mChildren.size();
for (int i = 0; i < childCount; i++) {
ExpandableNotificationRow child = mChildren.get(i);
float childHeight = child.isExpanded(true /* allowOnKeyguard */)
? child.getMaxExpandHeight()
: child.getShowingLayout().getMinHeight(true /* likeGroupExpanded */);
- float singleLineHeight = child.getShowingLayout().getMinHeight(
- false /* likeGroupExpanded */);
- child.setActualHeight((int) NotificationUtils.interpolate(singleLineHeight, childHeight,
- fraction), false);
+ if (i < maxAllowedVisibleChildren) {
+ float singleLineHeight = child.getShowingLayout().getMinHeight(
+ false /* likeGroupExpanded */);
+ child.setActualHeight((int) NotificationUtils.interpolate(singleLineHeight,
+ childHeight, fraction), false);
+ } else {
+ child.setActualHeight((int) childHeight, false);
+ }
}
}
- public float getChildExpandFraction() {
- int allChildrenVisibleHeight = getChildrenExpandStartHeight();
- int maxContentHeight = getMaxContentHeight();
- float factor = (mActualHeight - allChildrenVisibleHeight)
- / (float) (maxContentHeight - allChildrenVisibleHeight);
+ public float getGroupExpandFraction() {
+ int visibleChildrenExpandedHeight = getVisibleChildrenExpandHeight();
+ int minExpandHeight = getMinExpandHeight();
+ float factor = (mActualHeight - minExpandHeight)
+ / (float) (visibleChildrenExpandedHeight - minExpandHeight);
return Math.max(0.0f, Math.min(1.0f, factor));
}
- private int getChildrenExpandStartHeight() {
- int intrinsicHeight = mNotificationHeaderHeight;
+ private int getVisibleChildrenExpandHeight() {
+ int intrinsicHeight = mNotificationHeaderHeight + mNotificatonTopPadding + mDividerHeight;
int visibleChildren = 0;
int childCount = mChildren.size();
+ int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* forceCollapsed */);
for (int i = 0; i < childCount; i++) {
- if (visibleChildren >= NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED) {
+ if (visibleChildren >= maxAllowedVisibleChildren) {
break;
}
ExpandableNotificationRow child = mChildren.get(i);
- intrinsicHeight += child.getMinHeight();
+ float childHeight = child.isExpanded(true /* allowOnKeyguard */)
+ ? child.getMaxExpandHeight()
+ : child.getShowingLayout().getMinHeight(true /* likeGroupExpanded */);
+ intrinsicHeight += childHeight;
visibleChildren++;
}
- if (visibleChildren > 0) {
- intrinsicHeight += (visibleChildren - 1) * mChildPadding;
- }
- intrinsicHeight += mCollapsedBottompadding;
return intrinsicHeight;
}
@@ -574,9 +621,8 @@
return getIntrinsicHeight(NUMBER_OF_CHILDREN_WHEN_COLLAPSED);
}
- public int getMinExpandHeight(boolean onKeyguard) {
- int maxAllowedVisibleChildren = onKeyguard ? NUMBER_OF_CHILDREN_WHEN_COLLAPSED
- : getMaxAllowedVisibleChildren(true /* forceCollapsed */);
+ public int getMinExpandHeight() {
+ int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* forceCollapsed */);
int minExpandHeight = mNotificationHeaderHeight;
int visibleChildren = 0;
boolean firstChild = true;
@@ -599,7 +645,7 @@
}
public void setDark(boolean dark, boolean fade, long delay) {
- if (mGroupOverflowContainer != null) {
+ if (mOverflowNumber != null) {
mOverflowInvertHelper.setInverted(dark, fade, delay);
}
}
@@ -624,4 +670,9 @@
child.setUserLocked(userLocked);
}
}
+
+ public void onNotificationUpdated() {
+ mHybridGroupManager.setOverflowNumberColor(mOverflowNumber,
+ mNotificationParent.getNotificationColor());
+ }
}
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 686a712..bd5dcc6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -694,11 +694,7 @@
mHeadsUpManager.addSwipedOutNotification(row.getStatusBarNotification().getKey());
}
}
- final View veto = v.findViewById(R.id.veto);
- if (veto != null && veto.getVisibility() != View.GONE) {
- veto.performClick();
- }
- if (DEBUG) Log.v(TAG, "onChildDismissed: " + v);
+ performDismiss(v);
mFalsingManager.onNotificationDismissed();
if (mFalsingManager.shouldEnforceBouncer()) {
@@ -707,6 +703,24 @@
}
}
+ private void performDismiss(View v) {
+ if (v instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ if (mGroupManager.isOnlyChildInSuppressedGroup(row.getStatusBarNotification())) {
+ ExpandableNotificationRow groupSummary =
+ mGroupManager.getLogicalGroupSummary(row.getStatusBarNotification());
+ if (groupSummary.isClearable()) {
+ performDismiss(groupSummary);
+ }
+ }
+ }
+ final View veto = v.findViewById(R.id.veto);
+ if (veto != null && veto.getVisibility() != View.GONE) {
+ veto.performClick();
+ }
+ if (DEBUG) Log.v(TAG, "onChildDismissed: " + v);
+ }
+
@Override
public void onChildSnappedBack(View animView, float targetLeft) {
mAmbientState.onDragFinished(animView);
@@ -3265,7 +3279,7 @@
}
@Override
- public void onChildIsolationChanged() {
+ public void onGroupsChanged() {
mPhoneStatusBar.requestNotificationUpdate();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
index cf4802d..dba5bbd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
@@ -972,10 +972,10 @@
}
/**
- * Get the end value of the height animation running on a view or the actualHeight
+ * Get the end value of the yTranslation animation running on a view or the yTranslation
* if no animation is running.
*/
- public static float getFinalTranslationY(ExpandableView view) {
+ public static float getFinalTranslationY(View view) {
if (view == null) {
return 0;
}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
index ff7ea27..95cee4c 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
@@ -120,6 +120,14 @@
mMediaSessionManager.addOnActiveSessionsChangedListener(
mActiveMediaSessionListener, null);
updateMediaController(mMediaSessionManager.getActiveSessions(null));
+ if (mIsRecentsShown) {
+ // If an activity becomes PIPed again after the fullscreen, the Recents is shown
+ // behind so we need to resize the pinned stack and show the correct overlay.
+ resizePinnedStack(STATE_PIP_OVERLAY);
+ }
+ for (int i = mListeners.size() - 1; i >= 0; i--) {
+ mListeners.get(i).onPipEntered();
+ }
}
};
private final Runnable mOnTaskStackChanged = new Runnable() {
@@ -152,6 +160,7 @@
private final Runnable mOnPinnedActivityRestartAttempt = new Runnable() {
@Override
public void run() {
+ // If PIPed activity is launched again by Launcher or intent, make it fullscreen.
movePipToFullscreen();
}
};
@@ -315,11 +324,7 @@
private void showPipOverlay() {
if (DEBUG) Log.d(TAG, "showPipOverlay()");
mState = STATE_PIP_OVERLAY;
- Intent intent = new Intent(mContext, PipOverlayActivity.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- final ActivityOptions options = ActivityOptions.makeBasic();
- options.setLaunchStackId(PINNED_STACK_ID);
- mContext.startActivity(intent, options.toBundle());
+ PipOverlayActivity.showPipOverlay(mContext);
}
/**
@@ -385,9 +390,9 @@
break;
}
try {
- mActivityManager.resizeStack(PINNED_STACK_ID, mCurrentPipBounds, true, true, true);
+ mActivityManager.resizeStack(PINNED_STACK_ID, mCurrentPipBounds, true, true, true, -1);
} catch (RemoteException e) {
- Log.e(TAG, "showPipMenu failed", e);
+ Log.e(TAG, "resizeStack failed", e);
}
}
@@ -485,17 +490,7 @@
* Returns {@code true} if PIP is shown.
*/
public boolean isPipShown() {
- return hasPipTasks();
- }
-
- private boolean hasPipTasks() {
- try {
- StackInfo stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
- return stackInfo != null;
- } catch (RemoteException e) {
- Log.e(TAG, "getStackInfo failed", e);
- return false;
- }
+ return mState != STATE_NO_PIP;
}
private void handleMediaResourceGranted(String[] packageNames) {
@@ -603,6 +598,13 @@
* A listener interface to receive notification on changes in PIP.
*/
public interface Listener {
+ /**
+ * Invoked when an activity is pinned and PIP manager is set corresponding information.
+ * Classes must use this instead of {@link android.app.ITaskStackListener.onActivityPinned}
+ * because there's no guarantee for the PIP manager be return relavent information
+ * correctly. (e.g. {@link isPipShown}, {@link getPipBounds})
+ */
+ void onPipEntered();
/** Invoked when a PIPed activity is closed. */
void onPipActivityClosed();
/** Invoked when the PIP menu gets shown. */
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java
index 285dfd1..b8b837a 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java
@@ -198,6 +198,9 @@
}
@Override
+ public void onPipEntered() { }
+
+ @Override
public void onPipActivityClosed() {
finish();
}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java
index ad45625b..79daf3d 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java
@@ -67,6 +67,9 @@
}
@Override
+ public void onPipEntered() { }
+
+ @Override
public void onPipActivityClosed() {
finish();
}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
index 95d655c..1de321d 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
@@ -17,21 +17,31 @@
package com.android.systemui.tv.pip;
import android.app.Activity;
+import android.app.ActivityOptions;
+import android.content.Context;
+import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import com.android.systemui.R;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+
/**
* Activity to show an overlay on top of PIP activity to show how to pop up PIP menu.
*/
public class PipOverlayActivity extends Activity implements PipManager.Listener {
- private static final String TAG = "PipOverlayActivity";
- private static final boolean DEBUG = false;
-
private static final long SHOW_GUIDE_OVERLAY_VIEW_DURATION_MS = 4000;
+ /**
+ * The single instance of PipOverlayActivity to prevent it from restarting.
+ * Note that {@link PipManager} moves the PIPed activity to fullscreen if the activity is
+ * restarted. It's because the activity may be started by the Launcher or an intent again,
+ * but we don't want do so for the PipOverlayActivity.
+ */
+ private static PipOverlayActivity sPipOverlayActivity;
+
private final PipManager mPipManager = PipManager.getInstance();
private final Handler mHandler = new Handler();
private View mGuideOverlayView;
@@ -42,6 +52,19 @@
}
};
+ /**
+ * Launches the PIP overlay. This should be only called on the main thread.
+ */
+ public static void showPipOverlay(Context context) {
+ if (sPipOverlayActivity == null) {
+ Intent intent = new Intent(context, PipOverlayActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchStackId(PINNED_STACK_ID);
+ context.startActivity(intent, options.toBundle());
+ }
+ }
+
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
@@ -49,6 +72,8 @@
mGuideOverlayView = findViewById(R.id.guide_overlay);
mGuideButtonsView = findViewById(R.id.guide_buttons);
mPipManager.addListener(this);
+
+ sPipOverlayActivity = this;
}
@Override
@@ -76,6 +101,7 @@
@Override
protected void onDestroy() {
super.onDestroy();
+ sPipOverlayActivity = null;
mHandler.removeCallbacksAndMessages(null);
mPipManager.removeListener(this);
mPipManager.resumePipResizing(
@@ -83,6 +109,9 @@
}
@Override
+ public void onPipEntered() { }
+
+ @Override
public void onPipActivityClosed() {
finish();
}
@@ -105,6 +134,11 @@
}
@Override
- public void onMediaControllerChanged() {
+ public void onMediaControllerChanged() { }
+
+ @Override
+ public void finish() {
+ sPipOverlayActivity = null;
+ super.finish();
}
}
diff --git a/rs/java/android/renderscript/RenderScript.java b/rs/java/android/renderscript/RenderScript.java
index 425569c..9beaba30 100644
--- a/rs/java/android/renderscript/RenderScript.java
+++ b/rs/java/android/renderscript/RenderScript.java
@@ -1387,6 +1387,27 @@
}
/**
+ * Name of the file that holds the object cache.
+ */
+ private static String mCachePath;
+
+ /**
+ * Gets the path to the code cache.
+ */
+ static synchronized String getCachePath() {
+ if (mCachePath == null) {
+ final String CACHE_PATH = "com.android.renderscript.cache";
+ if (RenderScriptCacheDir.mCacheDir == null) {
+ throw new RSRuntimeException("RenderScript code cache directory uninitialized.");
+ }
+ File f = new File(RenderScriptCacheDir.mCacheDir, CACHE_PATH);
+ mCachePath = f.getAbsolutePath();
+ f.mkdirs();
+ }
+ return mCachePath;
+ }
+
+ /**
* Create a RenderScript context.
*
* @param ctx The context.
@@ -1415,11 +1436,7 @@
}
// set up cache directory for entire context
- final String CACHE_PATH = "com.android.renderscript.cache";
- File f = new File(RenderScriptCacheDir.mCacheDir, CACHE_PATH);
- String mCachePath = f.getAbsolutePath();
- f.mkdirs();
- rs.nContextSetCacheDir(mCachePath);
+ rs.nContextSetCacheDir(RenderScript.getCachePath());
rs.mMessageThread = new MessageThread(rs);
rs.mMessageThread.start();
diff --git a/rs/java/android/renderscript/ScriptC.java b/rs/java/android/renderscript/ScriptC.java
index bf706c1..00ebe57 100644
--- a/rs/java/android/renderscript/ScriptC.java
+++ b/rs/java/android/renderscript/ScriptC.java
@@ -84,13 +84,6 @@
setID(id);
}
- /**
- * Name of the file that holds the object cache.
- */
- private static final String CACHE_PATH = "com.android.renderscript.cache";
-
- static String mCachePath;
-
private static synchronized long internalCreate(RenderScript rs, Resources resources, int resourceID) {
byte[] pgm;
int pgmLength;
@@ -122,26 +115,12 @@
String resName = resources.getResourceEntryName(resourceID);
- // Create the RS cache path if we haven't done so already.
- if (mCachePath == null) {
- File f = new File(RenderScriptCacheDir.mCacheDir, CACHE_PATH);
- mCachePath = f.getAbsolutePath();
- f.mkdirs();
- }
// Log.v(TAG, "Create script for resource = " + resName);
- return rs.nScriptCCreate(resName, mCachePath, pgm, pgmLength);
+ return rs.nScriptCCreate(resName, RenderScript.getCachePath(), pgm, pgmLength);
}
private static synchronized long internalStringCreate(RenderScript rs, String resName, byte[] bitcode) {
- // Create the RS cache path if we haven't done so already.
- if (mCachePath == null) {
- File f = new File(RenderScriptCacheDir.mCacheDir, CACHE_PATH);
- mCachePath = f.getAbsolutePath();
- f.mkdirs();
- }
// Log.v(TAG, "Create script for resource = " + resName);
- return rs.nScriptCCreate(resName, mCachePath, bitcode, bitcode.length);
+ return rs.nScriptCCreate(resName, RenderScript.getCachePath(), bitcode, bitcode.length);
}
-
-
}
diff --git a/rs/java/android/renderscript/ScriptGroup.java b/rs/java/android/renderscript/ScriptGroup.java
index 9bbacbc..9357c3bb 100644
--- a/rs/java/android/renderscript/ScriptGroup.java
+++ b/rs/java/android/renderscript/ScriptGroup.java
@@ -396,7 +396,7 @@
for (int i = 0; i < closureIDs.length; i++) {
closureIDs[i] = closures.get(i).getID(rs);
}
- long id = rs.nScriptGroup2Create(name, ScriptC.mCachePath, closureIDs);
+ long id = rs.nScriptGroup2Create(name, RenderScript.getCachePath(), closureIDs);
setID(id);
}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 8febecc..215be4a 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -332,7 +332,12 @@
for (int i = N - 1; i >= 0; i--) {
Provider provider = installedProviders.get(i);
- ensureGroupStateLoadedLocked(provider.getUserId());
+ final int userId = provider.getUserId();
+ if (!mUserManager.isUserUnlocked(userId) ||
+ isProfileWithLockedParent(userId)) {
+ continue;
+ }
+ ensureGroupStateLoadedLocked(userId);
if (!removedProviders.contains(provider.id)) {
final boolean changed = updateProvidersForPackageLocked(
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index a94c8b8..e7db2a8 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -447,12 +447,8 @@
int[] ops) {
mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
Binder.getCallingPid(), Binder.getCallingUid(), null);
- String resolvedPackageName = resolvePackageName(uid, packageName);
- if (resolvedPackageName == null) {
- return Collections.emptyList();
- }
synchronized (this) {
- Ops pkgOps = getOpsRawLocked(uid, resolvedPackageName, false);
+ Ops pkgOps = getOpsLocked(uid, packageName, false);
if (pkgOps == null) {
return null;
}
@@ -470,7 +466,7 @@
private void pruneOp(Op op, int uid, String packageName) {
if (op.time == 0 && op.rejectTime == 0) {
- Ops ops = getOpsRawLocked(uid, packageName, false);
+ Ops ops = getOpsLocked(uid, packageName, false);
if (ops != null) {
ops.remove(op.op);
if (ops.size() <= 0) {
@@ -884,12 +880,8 @@
public int checkOperation(int code, int uid, String packageName) {
verifyIncomingUid(uid);
verifyIncomingOp(code);
- String resolvedPackageName = resolvePackageName(uid, packageName);
- if (resolvedPackageName == null) {
- return AppOpsManager.MODE_IGNORED;
- }
synchronized (this) {
- if (isOpRestricted(uid, code, resolvedPackageName)) {
+ if (isOpRestricted(uid, code, packageName)) {
return AppOpsManager.MODE_IGNORED;
}
code = AppOpsManager.opToSwitch(code);
@@ -900,7 +892,7 @@
return uidMode;
}
}
- Op op = getOpLocked(code, uid, resolvedPackageName, false);
+ Op op = getOpLocked(code, uid, packageName, false);
if (op == null) {
return AppOpsManager.opToDefaultMode(code);
}
@@ -976,7 +968,6 @@
@Override
public int checkPackage(int uid, String packageName) {
- Preconditions.checkNotNull(packageName);
synchronized (this) {
if (getOpsRawLocked(uid, packageName, true) != null) {
return AppOpsManager.MODE_ALLOWED;
@@ -990,39 +981,26 @@
public int noteProxyOperation(int code, String proxyPackageName,
int proxiedUid, String proxiedPackageName) {
verifyIncomingOp(code);
- final int proxyUid = Binder.getCallingUid();
- String resolveProxyPackageName = resolvePackageName(proxyUid, proxyPackageName);
- if (resolveProxyPackageName == null) {
- return AppOpsManager.MODE_IGNORED;
- }
- final int proxyMode = noteOperationUnchecked(code, proxyUid,
- resolveProxyPackageName, -1, null);
+ final int proxyMode = noteOperationUnchecked(code, Binder.getCallingUid(),
+ proxyPackageName, -1, null);
if (proxyMode != AppOpsManager.MODE_ALLOWED || Binder.getCallingUid() == proxiedUid) {
return proxyMode;
}
- String resolveProxiedPackageName = resolvePackageName(proxiedUid, proxiedPackageName);
- if (resolveProxiedPackageName == null) {
- return AppOpsManager.MODE_IGNORED;
- }
- return noteOperationUnchecked(code, proxiedUid, resolveProxiedPackageName,
- proxyMode, resolveProxyPackageName);
+ return noteOperationUnchecked(code, proxiedUid, proxiedPackageName,
+ Binder.getCallingUid(), proxyPackageName);
}
@Override
public int noteOperation(int code, int uid, String packageName) {
verifyIncomingUid(uid);
verifyIncomingOp(code);
- String resolvedPackageName = resolvePackageName(uid, packageName);
- if (resolvedPackageName == null) {
- return AppOpsManager.MODE_IGNORED;
- }
- return noteOperationUnchecked(code, uid, resolvedPackageName, 0, null);
+ return noteOperationUnchecked(code, uid, packageName, 0, null);
}
private int noteOperationUnchecked(int code, int uid, String packageName,
int proxyUid, String proxyPackageName) {
synchronized (this) {
- Ops ops = getOpsRawLocked(uid, packageName, true);
+ Ops ops = getOpsLocked(uid, packageName, true);
if (ops == null) {
if (DEBUG) Log.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
+ " package " + packageName);
@@ -1070,20 +1048,16 @@
public int startOperation(IBinder token, int code, int uid, String packageName) {
verifyIncomingUid(uid);
verifyIncomingOp(code);
- String resolvedPackageName = resolvePackageName(uid, packageName);
- if (resolvedPackageName == null) {
- return AppOpsManager.MODE_IGNORED;
- }
ClientState client = (ClientState)token;
synchronized (this) {
- Ops ops = getOpsRawLocked(uid, resolvedPackageName, true);
+ Ops ops = getOpsLocked(uid, packageName, true);
if (ops == null) {
if (DEBUG) Log.d(TAG, "startOperation: no op for code " + code + " uid " + uid
- + " package " + resolvedPackageName);
+ + " package " + packageName);
return AppOpsManager.MODE_ERRORED;
}
Op op = getOpLocked(ops, code, true);
- if (isOpRestricted(uid, code, resolvedPackageName)) {
+ if (isOpRestricted(uid, code, packageName)) {
return AppOpsManager.MODE_IGNORED;
}
final int switchCode = AppOpsManager.opToSwitch(code);
@@ -1093,7 +1067,7 @@
if (uidMode != AppOpsManager.MODE_ALLOWED) {
if (DEBUG) Log.d(TAG, "noteOperation: reject #" + op.mode + " for code "
+ switchCode + " (" + code + ") uid " + uid + " package "
- + resolvedPackageName);
+ + packageName);
op.rejectTime = System.currentTimeMillis();
return uidMode;
}
@@ -1101,13 +1075,12 @@
final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, true) : op;
if (switchOp.mode != AppOpsManager.MODE_ALLOWED) {
if (DEBUG) Log.d(TAG, "startOperation: reject #" + op.mode + " for code "
- + switchCode + " (" + code + ") uid " + uid + " package "
- + resolvedPackageName);
+ + switchCode + " (" + code + ") uid " + uid + " package " + packageName);
op.rejectTime = System.currentTimeMillis();
return switchOp.mode;
}
if (DEBUG) Log.d(TAG, "startOperation: allowing code " + code + " uid " + uid
- + " package " + resolvedPackageName);
+ + " package " + packageName);
if (op.nesting == 0) {
op.time = System.currentTimeMillis();
op.rejectTime = 0;
@@ -1125,16 +1098,9 @@
public void finishOperation(IBinder token, int code, int uid, String packageName) {
verifyIncomingUid(uid);
verifyIncomingOp(code);
- String resolvedPackageName = resolvePackageName(uid, packageName);
- if (resolvedPackageName == null) {
- return;
- }
- if (!(token instanceof ClientState)) {
- return;
- }
- ClientState client = (ClientState) token;
+ ClientState client = (ClientState)token;
synchronized (this) {
- Op op = getOpLocked(code, uid, resolvedPackageName, true);
+ Op op = getOpLocked(code, uid, packageName, true);
if (op == null) {
return;
}
@@ -1150,9 +1116,6 @@
@Override
public int permissionToOpCode(String permission) {
- if (permission == null) {
- return AppOpsManager.OP_NONE;
- }
return AppOpsManager.permissionToOpCode(permission);
}
@@ -1202,6 +1165,15 @@
return uidState;
}
+ private Ops getOpsLocked(int uid, String packageName, boolean edit) {
+ if (uid == 0) {
+ packageName = "root";
+ } else if (uid == Process.SHELL_UID) {
+ packageName = "com.android.shell";
+ }
+ return getOpsRawLocked(uid, packageName, edit);
+ }
+
private Ops getOpsRawLocked(int uid, String packageName, boolean edit) {
UidState uidState = getUidStateLocked(uid, edit);
if (uidState == null) {
@@ -1287,7 +1259,7 @@
}
private Op getOpLocked(int code, int uid, String packageName, boolean edit) {
- Ops ops = getOpsRawLocked(uid, packageName, edit);
+ Ops ops = getOpsLocked(uid, packageName, edit);
if (ops == null) {
return null;
}
@@ -1345,7 +1317,7 @@
if (AppOpsManager.opAllowSystemBypassRestriction(code)) {
// If we are the system, bypass user restrictions for certain codes
synchronized (this) {
- Ops ops = getOpsRawLocked(uid, packageName, true);
+ Ops ops = getOpsLocked(uid, packageName, true);
if ((ops != null) && ops.isPrivileged) {
return false;
}
@@ -1610,7 +1582,7 @@
out.startTag(null, "uid");
out.attribute(null, "n", Integer.toString(pkg.getUid()));
synchronized (this) {
- Ops ops = getOpsRawLocked(pkg.getUid(), pkg.getPackageName(), false);
+ Ops ops = getOpsLocked(pkg.getUid(), pkg.getPackageName(), false);
// Should always be present as the list of PackageOps is generated
// from Ops.
if (ops != null) {
@@ -2131,7 +2103,6 @@
@Override
public void setUserRestrictions(Bundle restrictions, IBinder token, int userHandle) {
checkSystemUid("setUserRestrictions");
- Preconditions.checkNotNull(restrictions);
Preconditions.checkNotNull(token);
final boolean[] opRestrictions = getOrCreateUserRestrictionsForToken(token, userHandle);
for (int i = 0; i < opRestrictions.length; ++i) {
@@ -2346,15 +2317,6 @@
}
}
- private static String resolvePackageName(int uid, String packageName) {
- if (uid == 0) {
- return "root";
- } else if (uid == Process.SHELL_UID) {
- return "com.android.shell";
- }
- return packageName;
- }
-
private static String[] getPackagesForUid(int uid) {
String[] packageNames = null;
try {
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index b7fca1a..5b01062 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -3845,8 +3845,16 @@
@Override
public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities,
Messenger messenger, int timeoutMs, IBinder binder, int legacyType) {
- networkCapabilities = new NetworkCapabilities(networkCapabilities);
- enforceNetworkRequestPermissions(networkCapabilities);
+ // If the requested networkCapabilities is null, take them instead from
+ // the default network request. This allows callers to keep track of
+ // the system default network.
+ if (networkCapabilities == null) {
+ networkCapabilities = new NetworkCapabilities(mDefaultRequest.networkCapabilities);
+ enforceAccessPermission();
+ } else {
+ networkCapabilities = new NetworkCapabilities(networkCapabilities);
+ enforceNetworkRequestPermissions(networkCapabilities);
+ }
enforceMeteredApnPolicy(networkCapabilities);
ensureRequestableCapabilities(networkCapabilities);
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index 65a22b9..ccca5ba 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -1946,6 +1946,10 @@
throw new IllegalStateException(
"Emulation not available on device with native FBE");
}
+ if (mLockPatternUtils.isCredentialRequiredToDecrypt(false)) {
+ throw new IllegalStateException(
+ "Emulation requires disabling 'Secure start-up' in Settings > Security");
+ }
final long token = Binder.clearCallingIdentity();
try {
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 07c10b0..548b662 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -1149,81 +1149,6 @@
}
@Override
- public RouteInfo[] getRoutes(String interfaceName) {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- ArrayList<RouteInfo> routes = new ArrayList<RouteInfo>();
-
- // v4 routes listed as:
- // iface dest-addr gateway-addr flags refcnt use metric netmask mtu window IRTT
- for (String s : readRouteList("/proc/net/route")) {
- String[] fields = s.split("\t");
-
- if (fields.length > 7) {
- String iface = fields[0];
-
- if (interfaceName.equals(iface)) {
- String dest = fields[1];
- String gate = fields[2];
- String flags = fields[3]; // future use?
- String mask = fields[7];
- try {
- // address stored as a hex string, ex: 0014A8C0
- InetAddress destAddr =
- NetworkUtils.intToInetAddress((int)Long.parseLong(dest, 16));
- int prefixLength =
- NetworkUtils.netmaskIntToPrefixLength(
- (int)Long.parseLong(mask, 16));
- LinkAddress linkAddress = new LinkAddress(destAddr, prefixLength);
-
- // address stored as a hex string, ex 0014A8C0
- InetAddress gatewayAddr =
- NetworkUtils.intToInetAddress((int)Long.parseLong(gate, 16));
-
- RouteInfo route = new RouteInfo(linkAddress, gatewayAddr);
- routes.add(route);
- } catch (Exception e) {
- Log.e(TAG, "Error parsing route " + s + " : " + e);
- continue;
- }
- }
- }
- }
-
- // v6 routes listed as:
- // dest-addr prefixlength ?? ?? gateway-addr ?? ?? ?? ?? iface
- for (String s : readRouteList("/proc/net/ipv6_route")) {
- String[]fields = s.split("\\s+");
- if (fields.length > 9) {
- String iface = fields[9].trim();
- if (interfaceName.equals(iface)) {
- String dest = fields[0];
- String prefix = fields[1];
- String gate = fields[4];
-
- try {
- // prefix length stored as a hex string, ex 40
- int prefixLength = Integer.parseInt(prefix, 16);
-
- // address stored as a 32 char hex string
- // ex fe800000000000000000000000000000
- InetAddress destAddr = NetworkUtils.hexToInet6Address(dest);
- LinkAddress linkAddress = new LinkAddress(destAddr, prefixLength);
-
- InetAddress gateAddr = NetworkUtils.hexToInet6Address(gate);
-
- RouteInfo route = new RouteInfo(linkAddress, gateAddr);
- routes.add(route);
- } catch (Exception e) {
- Log.e(TAG, "Error parsing route " + s + " : " + e);
- continue;
- }
- }
- }
- }
- return routes.toArray(new RouteInfo[routes.size()]);
- }
-
- @Override
public void setMtu(String iface, int mtu) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 63a0e87..f51fb6c 100755
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -811,10 +811,8 @@
// Hacky kind of thing -- allow system stuff to tell us
// what they are, so we can report this elsewhere for
// others to know why certain services are running.
- try {
- clientIntent = service.getParcelableExtra(Intent.EXTRA_CLIENT_INTENT);
- } catch (RuntimeException e) {
- }
+ service.setDefusable(true);
+ clientIntent = service.getParcelableExtra(Intent.EXTRA_CLIENT_INTENT);
if (clientIntent != null) {
clientLabel = service.getIntExtra(Intent.EXTRA_CLIENT_LABEL, 0);
if (clientLabel != 0) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5aac43d..c1ec71c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -6923,6 +6923,7 @@
intents[i].setDefusable(true);
}
}
+ Bundle.setDefusable(bOptions, true);
final boolean noCreate = (flags&PendingIntent.FLAG_NO_CREATE) != 0;
final boolean cancelCurrent = (flags&PendingIntent.FLAG_CANCEL_CURRENT) != 0;
@@ -9673,14 +9674,14 @@
@Override
public void resizeStack(int stackId, Rect bounds, boolean allowResizeInDockedMode,
- boolean preserveWindows, boolean animate) {
+ boolean preserveWindows, boolean animate, int animationDuration) {
enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "resizeStack()");
long ident = Binder.clearCallingIdentity();
try {
synchronized (this) {
if (animate) {
if (stackId == PINNED_STACK_ID) {
- mWindowManager.animateResizePinnedStack(bounds);
+ mWindowManager.animateResizePinnedStack(bounds, animationDuration);
} else {
throw new IllegalArgumentException("Stack: " + stackId
+ " doesn't support animated resize.");
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index d364d85..7def1bd 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2408,7 +2408,7 @@
ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
resumeFocusedStackTopActivityLocked();
- mWindowManager.animateResizePinnedStack(bounds);
+ mWindowManager.animateResizePinnedStack(bounds, -1);
mService.notifyActivityPinnedLocked();
}
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index efa7420..af69c93 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -1483,7 +1483,8 @@
if (mLaunchBounds != null) {
final int stackId = mTargetStack.mStackId;
if (StackId.resizeStackWithLaunchBounds(stackId)) {
- mService.resizeStack(stackId, mLaunchBounds, true, !PRESERVE_WINDOWS, ANIMATE);
+ mService.resizeStack(
+ stackId, mLaunchBounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
} else {
mStartActivity.task.updateOverrideConfiguration(mLaunchBounds);
}
@@ -1571,7 +1572,7 @@
stackId = stack.mStackId;
}
if (StackId.resizeStackWithLaunchBounds(stackId)) {
- mService.resizeStack(stackId, mLaunchBounds, true, !PRESERVE_WINDOWS, ANIMATE);
+ mService.resizeStack(stackId, mLaunchBounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
}
}
mTargetStack = mInTask.stack;
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 8039072..b8f45bc 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -208,7 +208,10 @@
String requiredPermission, IBinder resultTo, String resultWho, int requestCode,
int flagsMask, int flagsValues, Bundle options, IActivityContainer container)
throws TransactionTooLargeException {
- synchronized(owner) {
+ if (intent != null) intent.setDefusable(true);
+ if (options != null) options.setDefusable(true);
+
+ synchronized (owner) {
final ActivityContainer activityContainer = (ActivityContainer)container;
if (activityContainer != null && activityContainer.mParentActivity != null &&
activityContainer.mParentActivity.state
diff --git a/services/core/java/com/android/server/connectivity/ApfFilter.java b/services/core/java/com/android/server/connectivity/ApfFilter.java
index d62a0b3..0014665 100644
--- a/services/core/java/com/android/server/connectivity/ApfFilter.java
+++ b/services/core/java/com/android/server/connectivity/ApfFilter.java
@@ -90,6 +90,7 @@
}
private static final String TAG = "ApfFilter";
+ private static final boolean DBG = true;
private static final boolean VDBG = false;
private final ConnectivityService mConnectivityService;
@@ -205,6 +206,10 @@
// For debugging only. How many times this RA was seen.
int seenCount = 0;
+ // For debugging only. Returns the hex representation of the last matching packet.
+ String getLastMatchingPacket() {
+ return HexDump.toHexString(mPacket.array(), 0, mPacket.capacity(), false /* lowercase */);
+ }
private String IPv6AddresstoString(int pos) {
try {
@@ -454,7 +459,7 @@
private long mLastInstalledProgramMinLifetime;
// For debugging only. The length in bytes of the last program.
- private long mLastInstalledProgramLength;
+ private byte[] mLastInstalledProgram;
private void installNewProgram() {
if (mRas.size() == 0) return;
@@ -495,7 +500,7 @@
}
mLastTimeInstalledProgram = curTime();
mLastInstalledProgramMinLifetime = programMinLifetime;
- mLastInstalledProgramLength = program.length;
+ mLastInstalledProgram = program;
if (VDBG) {
hexDump("Installing filter: ", program, program.length);
} else {
@@ -515,7 +520,7 @@
}
private void hexDump(String msg, byte[] packet, int length) {
- log(msg + HexDump.toHexString(packet, 0, length));
+ log(msg + HexDump.toHexString(packet, 0, length, false /* lowercase */));
}
private void processRa(byte[] packet, int length) {
@@ -608,7 +613,7 @@
pw.println(String.format(
"Last program length %d, installed %ds ago, lifetime %d",
- mLastInstalledProgramLength, curTime() - mLastTimeInstalledProgram,
+ mLastInstalledProgram.length, curTime() - mLastTimeInstalledProgram,
mLastInstalledProgramMinLifetime));
pw.println("RA filters:");
@@ -618,8 +623,22 @@
pw.increaseIndent();
pw.println(String.format(
"Seen: %d, last %ds ago", ra.seenCount, curTime() - ra.mLastSeen));
+ if (DBG) {
+ pw.println("Last match:");
+ pw.increaseIndent();
+ pw.println(ra.getLastMatchingPacket());
+ pw.decreaseIndent();
+ }
pw.decreaseIndent();
}
+
+ if (DBG) {
+ pw.println("Last program:");
+ pw.increaseIndent();
+ pw.println(HexDump.toHexString(mLastInstalledProgram, false /* lowercase */));
+ pw.decreaseIndent();
+ }
+
pw.decreaseIndent();
}
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 0b4f7f0..49ae293 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -28,13 +28,12 @@
import static android.content.Intent.ACTION_USER_ADDED;
import static android.content.Intent.ACTION_USER_REMOVED;
import static android.content.Intent.EXTRA_UID;
-
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
-import static android.net.ConnectivityManager.TYPE_MOBILE;
-import static android.net.ConnectivityManager.TYPE_WIMAX;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_WIMAX;
import static android.net.ConnectivityManager.isNetworkTypeMobile;
import static android.net.NetworkPolicy.CYCLE_NONE;
import static android.net.NetworkPolicy.LIMIT_DISABLED;
@@ -47,11 +46,9 @@
import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW;
import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
import static android.net.NetworkPolicyManager.FIREWALL_RULE_DENY;
-import static android.net.NetworkPolicyManager.POLICY_ALLOW_BACKGROUND_BATTERY_SAVE;
import static android.net.NetworkPolicyManager.POLICY_NONE;
import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
-import static android.net.NetworkPolicyManager.RULE_REJECT_ALL;
import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
import static android.net.NetworkPolicyManager.RULE_UNKNOWN;
import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
@@ -80,6 +77,7 @@
import static com.android.internal.util.XmlUtils.writeLongAttribute;
import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED;
+
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.END_TAG;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
@@ -130,12 +128,12 @@
import android.os.INetworkManagementService;
import android.os.IPowerManager;
import android.os.Message;
-import android.os.ResultReceiver;
import android.os.MessageQueue.IdleHandler;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
@@ -157,8 +155,6 @@
import android.util.TrustedTime;
import android.util.Xml;
-import libcore.io.IoUtils;
-
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
@@ -169,6 +165,9 @@
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.SystemConfig;
+
+import libcore.io.IoUtils;
+
import com.google.android.collect.Lists;
import org.xmlpull.v1.XmlPullParser;
@@ -1446,9 +1445,11 @@
final NetworkTemplate template = new NetworkTemplate(networkTemplate,
subscriberId, networkId);
- mNetworkPolicy.put(template, new NetworkPolicy(template, cycleDay,
- cycleTimezone, warningBytes, limitBytes, lastWarningSnooze,
- lastLimitSnooze, metered, inferred));
+ if (template.isPersistable()) {
+ mNetworkPolicy.put(template, new NetworkPolicy(template, cycleDay,
+ cycleTimezone, warningBytes, limitBytes, lastWarningSnooze,
+ lastLimitSnooze, metered, inferred));
+ }
} else if (TAG_UID_POLICY.equals(tag)) {
final int uid = readIntAttribute(in, ATTR_UID);
@@ -1535,6 +1536,7 @@
for (int i = 0; i < mNetworkPolicy.size(); i++) {
final NetworkPolicy policy = mNetworkPolicy.valueAt(i);
final NetworkTemplate template = policy.template;
+ if (!template.isPersistable()) continue;
out.startTag(null, TAG_NETWORK_POLICY);
writeIntAttribute(out, ATTR_NETWORK_TEMPLATE, template.getMatchRule());
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 274a73f..0f23fde 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1852,16 +1852,11 @@
}
private boolean checkPolicyAccess(String pkg) {
- if (PackageManager.PERMISSION_GRANTED == getContext().checkCallingPermission(
- android.Manifest.permission.MANAGE_NOTIFICATIONS)) {
+ if (PackageManager.PERMISSION_GRANTED == ActivityManager.checkComponentPermission(
+ android.Manifest.permission.MANAGE_NOTIFICATIONS, Binder.getCallingUid(),
+ -1, true)) {
return true;
}
- if (mAudioManagerInternal != null) {
- final int vcuid = mAudioManagerInternal.getVolumeControllerUid();
- if (vcuid > 0 && Binder.getCallingUid() == vcuid) {
- return true;
- }
- }
return checkPackagePolicyAccess(pkg) || mListeners.isComponentEnabledForPackage(pkg);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c2e0992..9335116 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -10451,10 +10451,10 @@
void startCleaningPackages() {
// reader
+ if (!isExternalMediaAvailable()) {
+ return;
+ }
synchronized (mPackages) {
- if (!isExternalMediaAvailable()) {
- return;
- }
if (mSettings.mPackagesToBeCleaned.isEmpty()) {
return;
}
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
new file mode 100644
index 0000000..f1920c7
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -0,0 +1,199 @@
+/*
+ * 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.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.pm.ShortcutInfo;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * Launcher information used by {@link ShortcutService}.
+ */
+class ShortcutLauncher {
+ private static final String TAG = ShortcutService.TAG;
+
+ static final String TAG_ROOT = "launcher-pins";
+
+ private static final String TAG_PACKAGE = "package";
+ private static final String TAG_PIN = "pin";
+
+ private static final String ATTR_VALUE = "value";
+ private static final String ATTR_PACKAGE_NAME = "package-name";
+
+ @UserIdInt
+ final int mUserId;
+
+ @NonNull
+ final String mPackageName;
+
+ /**
+ * Package name -> IDs.
+ */
+ final private ArrayMap<String, ArraySet<String>> mPinnedShortcuts = new ArrayMap<>();
+
+ ShortcutLauncher(@UserIdInt int userId, @NonNull String packageName) {
+ mUserId = userId;
+ mPackageName = packageName;
+ }
+
+ public void pinShortcuts(@NonNull ShortcutService s, @NonNull String packageName,
+ @NonNull List<String> ids) {
+ final int idSize = ids.size();
+ if (idSize == 0) {
+ mPinnedShortcuts.remove(packageName);
+ } else {
+ final ArraySet<String> prevSet = mPinnedShortcuts.get(packageName);
+
+ // Pin shortcuts. Make sure only pin the ones that were visible to the caller.
+ // i.e. a non-dynamic, pinned shortcut by *other launchers* shouldn't be pinned here.
+
+ final ShortcutPackage packageShortcuts =
+ s.getPackageShortcutsLocked(packageName, mUserId);
+ final ArraySet<String> newSet = new ArraySet<>();
+
+ for (int i = 0; i < idSize; i++) {
+ final String id = ids.get(i);
+ final ShortcutInfo si = packageShortcuts.findShortcutById(id);
+ if (si == null) {
+ continue;
+ }
+ if (si.isDynamic() || (prevSet != null && prevSet.contains(id))) {
+ newSet.add(id);
+ }
+ }
+ mPinnedShortcuts.put(packageName, newSet);
+ }
+ s.getPackageShortcutsLocked(packageName, mUserId).refreshPinnedFlags(s);
+ }
+
+ /**
+ * Return the pinned shortcut IDs for the publisher package.
+ */
+ public ArraySet<String> getPinnedShortcutIds(@NonNull String packageName) {
+ return mPinnedShortcuts.get(packageName);
+ }
+
+ boolean cleanUpPackage(String packageName) {
+ return mPinnedShortcuts.remove(packageName) != null;
+ }
+
+ /**
+ * Persist.
+ */
+ public void saveToXml(XmlSerializer out) throws IOException {
+ final int size = mPinnedShortcuts.size();
+ if (size == 0) {
+ return; // Nothing to write.
+ }
+
+ out.startTag(null, TAG_ROOT);
+ ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME,
+ mPackageName);
+
+ for (int i = 0; i < size; i++) {
+ out.startTag(null, TAG_PACKAGE);
+ ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME,
+ mPinnedShortcuts.keyAt(i));
+
+ final ArraySet<String> ids = mPinnedShortcuts.valueAt(i);
+ final int idSize = ids.size();
+ for (int j = 0; j < idSize; j++) {
+ ShortcutService.writeTagValue(out, TAG_PIN, ids.valueAt(j));
+ }
+ out.endTag(null, TAG_PACKAGE);
+ }
+
+ out.endTag(null, TAG_ROOT);
+ }
+
+ /**
+ * Load.
+ */
+ public static ShortcutLauncher loadFromXml(XmlPullParser parser, int userId)
+ throws IOException, XmlPullParserException {
+ final String launcherPackageName = ShortcutService.parseStringAttribute(parser,
+ ATTR_PACKAGE_NAME);
+
+ final ShortcutLauncher ret = new ShortcutLauncher(userId, launcherPackageName);
+
+ ArraySet<String> ids = null;
+ final int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+ final int depth = parser.getDepth();
+ final String tag = parser.getName();
+ switch (tag) {
+ case TAG_PACKAGE: {
+ final String packageName = ShortcutService.parseStringAttribute(parser,
+ ATTR_PACKAGE_NAME);
+ ids = new ArraySet<>();
+ ret.mPinnedShortcuts.put(packageName, ids);
+ continue;
+ }
+ case TAG_PIN: {
+ ids.add(ShortcutService.parseStringAttribute(parser,
+ ATTR_VALUE));
+ continue;
+ }
+ }
+ throw ShortcutService.throwForInvalidTag(depth, tag);
+ }
+ return ret;
+ }
+
+ public void dump(@NonNull ShortcutService s, @NonNull PrintWriter pw, @NonNull String prefix) {
+ pw.println();
+
+ pw.print(prefix);
+ pw.print("Launcher: ");
+ pw.print(mPackageName);
+ pw.println();
+
+ final int size = mPinnedShortcuts.size();
+ for (int i = 0; i < size; i++) {
+ pw.println();
+
+ pw.print(prefix);
+ pw.print(" ");
+ pw.print("Package: ");
+ pw.println(mPinnedShortcuts.keyAt(i));
+
+ final ArraySet<String> ids = mPinnedShortcuts.valueAt(i);
+ final int idSize = ids.size();
+
+ for (int j = 0; j < idSize; j++) {
+ pw.print(prefix);
+ pw.print(" ");
+ pw.print(ids.valueAt(j));
+ pw.println();
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
new file mode 100644
index 0000000..d614251
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -0,0 +1,520 @@
+/*
+ * 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.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.os.PersistableBundle;
+import android.text.format.Formatter;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
+
+/**
+ * Package information used by {@link ShortcutService}.
+ */
+class ShortcutPackage {
+ private static final String TAG = ShortcutService.TAG;
+
+ static final String TAG_ROOT = "package";
+ private static final String TAG_INTENT_EXTRAS = "intent-extras";
+ private static final String TAG_EXTRAS = "extras";
+ private static final String TAG_SHORTCUT = "shortcut";
+
+ private static final String ATTR_NAME = "name";
+ private static final String ATTR_DYNAMIC_COUNT = "dynamic-count";
+ private static final String ATTR_CALL_COUNT = "call-count";
+ private static final String ATTR_LAST_RESET = "last-reset";
+ private static final String ATTR_ID = "id";
+ private static final String ATTR_ACTIVITY = "activity";
+ private static final String ATTR_TITLE = "title";
+ private static final String ATTR_INTENT = "intent";
+ private static final String ATTR_WEIGHT = "weight";
+ private static final String ATTR_TIMESTAMP = "timestamp";
+ private static final String ATTR_FLAGS = "flags";
+ private static final String ATTR_ICON_RES = "icon-res";
+ private static final String ATTR_BITMAP_PATH = "bitmap-path";
+
+ @UserIdInt
+ final int mUserId;
+
+ @NonNull
+ final String mPackageName;
+
+ /**
+ * All the shortcuts from the package, keyed on IDs.
+ */
+ final private ArrayMap<String, ShortcutInfo> mShortcuts = new ArrayMap<>();
+
+ /**
+ * # of dynamic shortcuts.
+ */
+ private int mDynamicShortcutCount = 0;
+
+ /**
+ * # of times the package has called rate-limited APIs.
+ */
+ private int mApiCallCount;
+
+ /**
+ * When {@link #mApiCallCount} was reset last time.
+ */
+ private long mLastResetTime;
+
+ ShortcutPackage(int userId, String packageName) {
+ mUserId = userId;
+ mPackageName = packageName;
+ }
+
+ @Nullable
+ public ShortcutInfo findShortcutById(String id) {
+ return mShortcuts.get(id);
+ }
+
+ private ShortcutInfo deleteShortcut(@NonNull ShortcutService s,
+ @NonNull String id) {
+ final ShortcutInfo shortcut = mShortcuts.remove(id);
+ if (shortcut != null) {
+ s.removeIcon(mUserId, shortcut);
+ shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED);
+ }
+ return shortcut;
+ }
+
+ void addShortcut(@NonNull ShortcutService s, @NonNull ShortcutInfo newShortcut) {
+ deleteShortcut(s, newShortcut.getId());
+ s.saveIconAndFixUpShortcut(mUserId, newShortcut);
+ mShortcuts.put(newShortcut.getId(), newShortcut);
+ }
+
+ /**
+ * Add a shortcut, or update one with the same ID, with taking over existing flags.
+ *
+ * It checks the max number of dynamic shortcuts.
+ */
+ public void addDynamicShortcut(@NonNull ShortcutService s,
+ @NonNull ShortcutInfo newShortcut) {
+ newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC);
+
+ final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId());
+
+ final boolean wasPinned;
+ final int newDynamicCount;
+
+ if (oldShortcut == null) {
+ wasPinned = false;
+ newDynamicCount = mDynamicShortcutCount + 1; // adding a dynamic shortcut.
+ } else {
+ wasPinned = oldShortcut.isPinned();
+ if (oldShortcut.isDynamic()) {
+ newDynamicCount = mDynamicShortcutCount; // not adding a dynamic shortcut.
+ } else {
+ newDynamicCount = mDynamicShortcutCount + 1; // adding a dynamic shortcut.
+ }
+ }
+
+ // Make sure there's still room.
+ s.enforceMaxDynamicShortcuts(newDynamicCount);
+
+ // Okay, make it dynamic and add.
+ if (wasPinned) {
+ newShortcut.addFlags(ShortcutInfo.FLAG_PINNED);
+ }
+
+ addShortcut(s, newShortcut);
+ mDynamicShortcutCount = newDynamicCount;
+ }
+
+ /**
+ * Remove all shortcuts that aren't pinned nor dynamic.
+ */
+ private void removeOrphans(@NonNull ShortcutService s) {
+ ArrayList<String> removeList = null; // Lazily initialize.
+
+ for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+ final ShortcutInfo si = mShortcuts.valueAt(i);
+
+ if (si.isPinned() || si.isDynamic()) continue;
+
+ if (removeList == null) {
+ removeList = new ArrayList<>();
+ }
+ removeList.add(si.getId());
+ }
+ if (removeList != null) {
+ for (int i = removeList.size() - 1; i >= 0; i--) {
+ deleteShortcut(s, removeList.get(i));
+ }
+ }
+ }
+
+ /**
+ * Remove all dynamic shortcuts.
+ */
+ public void deleteAllDynamicShortcuts(@NonNull ShortcutService s) {
+ for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+ mShortcuts.valueAt(i).clearFlags(ShortcutInfo.FLAG_DYNAMIC);
+ }
+ removeOrphans(s);
+ mDynamicShortcutCount = 0;
+ }
+
+ /**
+ * Remove a dynamic shortcut by ID.
+ */
+ public void deleteDynamicWithId(@NonNull ShortcutService s, @NonNull String shortcutId) {
+ final ShortcutInfo oldShortcut = mShortcuts.get(shortcutId);
+
+ if (oldShortcut == null) {
+ return;
+ }
+ if (oldShortcut.isDynamic()) {
+ mDynamicShortcutCount--;
+ }
+ if (oldShortcut.isPinned()) {
+ oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
+ } else {
+ deleteShortcut(s, shortcutId);
+ }
+ }
+
+ /**
+ * Called after a launcher updates the pinned set. For each shortcut in this package,
+ * set FLAG_PINNED if any launcher has pinned it. Otherwise, clear it.
+ *
+ * <p>Then remove all shortcuts that are not dynamic and no longer pinned either.
+ */
+ public void refreshPinnedFlags(@NonNull ShortcutService s) {
+ // First, un-pin all shortcuts
+ for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+ mShortcuts.valueAt(i).clearFlags(ShortcutInfo.FLAG_PINNED);
+ }
+
+ // Then, for the pinned set for each launcher, set the pin flag one by one.
+ final ArrayMap<String, ShortcutLauncher> launchers =
+ s.getUserShortcutsLocked(mUserId).getLaunchers();
+
+ for (int l = launchers.size() - 1; l >= 0; l--) {
+ final ShortcutLauncher launcherShortcuts = launchers.valueAt(l);
+ final ArraySet<String> pinned = launcherShortcuts.getPinnedShortcutIds(mPackageName);
+
+ if (pinned == null || pinned.size() == 0) {
+ continue;
+ }
+ for (int i = pinned.size() - 1; i >= 0; i--) {
+ final ShortcutInfo si = mShortcuts.get(pinned.valueAt(i));
+ if (si == null) {
+ s.wtf("Shortcut not found");
+ } else {
+ si.addFlags(ShortcutInfo.FLAG_PINNED);
+ }
+ }
+ }
+
+ // Lastly, remove the ones that are no longer pinned nor dynamic.
+ removeOrphans(s);
+ }
+
+ /**
+ * Number of calls that the caller has made, since the last reset.
+ */
+ public int getApiCallCount(@NonNull ShortcutService s) {
+ final long last = s.getLastResetTimeLocked();
+
+ final long now = s.injectCurrentTimeMillis();
+ if (ShortcutService.isClockValid(now) && mLastResetTime > now) {
+ Slog.w(TAG, "Clock rewound");
+ // Clock rewound.
+ mLastResetTime = now;
+ mApiCallCount = 0;
+ return mApiCallCount;
+ }
+
+ // If not reset yet, then reset.
+ if (mLastResetTime < last) {
+ if (ShortcutService.DEBUG) {
+ Slog.d(TAG, String.format("My last reset=%d, now=%d, last=%d: resetting",
+ mLastResetTime, now, last));
+ }
+ mApiCallCount = 0;
+ mLastResetTime = last;
+ }
+ return mApiCallCount;
+ }
+
+ /**
+ * If the caller app hasn't been throttled yet, increment {@link #mApiCallCount}
+ * and return true. Otherwise just return false.
+ */
+ public boolean tryApiCall(@NonNull ShortcutService s) {
+ if (getApiCallCount(s) >= s.mMaxDailyUpdates) {
+ return false;
+ }
+ mApiCallCount++;
+ return true;
+ }
+
+ public void resetRateLimitingForCommandLine() {
+ mApiCallCount = 0;
+ mLastResetTime = 0;
+ }
+
+ /**
+ * Find all shortcuts that match {@code query}.
+ */
+ public void findAll(@NonNull ShortcutService s, @NonNull List<ShortcutInfo> result,
+ @Nullable Predicate<ShortcutInfo> query, int cloneFlag,
+ @Nullable String callingLauncher) {
+
+ // Set of pinned shortcuts by the calling launcher.
+ final ArraySet<String> pinnedByCallerSet = (callingLauncher == null) ? null
+ : s.getLauncherShortcuts(callingLauncher, mUserId)
+ .getPinnedShortcutIds(mPackageName);
+
+ for (int i = 0; i < mShortcuts.size(); i++) {
+ final ShortcutInfo si = mShortcuts.valueAt(i);
+
+ // If it's called by non-launcher (i.e. publisher, always include -> true.
+ // Otherwise, only include non-dynamic pinned one, if the calling launcher has pinned
+ // it.
+ final boolean isPinnedByCaller = (callingLauncher == null)
+ || ((pinnedByCallerSet != null) && pinnedByCallerSet.contains(si.getId()));
+ if (!si.isDynamic()) {
+ if (!si.isPinned()) {
+ s.wtf("Shortcut not pinned here");
+ continue;
+ }
+ if (!isPinnedByCaller) {
+ continue;
+ }
+ }
+ final ShortcutInfo clone = si.clone(cloneFlag);
+ // Fix up isPinned for the caller. Note we need to do it before the "test" callback,
+ // since it may check isPinned.
+ if (!isPinnedByCaller) {
+ clone.clearFlags(ShortcutInfo.FLAG_PINNED);
+ }
+ if (query == null || query.test(clone)) {
+ result.add(clone);
+ }
+ }
+ }
+
+ public void resetThrottling() {
+ mApiCallCount = 0;
+ }
+
+ public void dump(@NonNull ShortcutService s, @NonNull PrintWriter pw, @NonNull String prefix) {
+ pw.println();
+
+ pw.print(prefix);
+ pw.print("Package: ");
+ pw.print(mPackageName);
+ pw.println();
+
+ pw.print(prefix);
+ pw.print(" ");
+ pw.print("Calls: ");
+ pw.print(getApiCallCount(s));
+ pw.println();
+
+ // This should be after getApiCallCount(), which may update it.
+ pw.print(prefix);
+ pw.print(" ");
+ pw.print("Last reset: [");
+ pw.print(mLastResetTime);
+ pw.print("] ");
+ pw.print(s.formatTime(mLastResetTime));
+ pw.println();
+
+ pw.println(" Shortcuts:");
+ long totalBitmapSize = 0;
+ final ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts;
+ final int size = shortcuts.size();
+ for (int i = 0; i < size; i++) {
+ final ShortcutInfo si = shortcuts.valueAt(i);
+ pw.print(" ");
+ pw.println(si.toInsecureString());
+ if (si.getBitmapPath() != null) {
+ final long len = new File(si.getBitmapPath()).length();
+ pw.print(" ");
+ pw.print("bitmap size=");
+ pw.println(len);
+
+ totalBitmapSize += len;
+ }
+ }
+ pw.print(prefix);
+ pw.print(" ");
+ pw.print("Total bitmap size: ");
+ pw.print(totalBitmapSize);
+ pw.print(" (");
+ pw.print(Formatter.formatFileSize(s.mContext, totalBitmapSize));
+ pw.println(")");
+ }
+
+ public void saveToXml(@NonNull XmlSerializer out) throws IOException, XmlPullParserException {
+ final int size = mShortcuts.size();
+
+ if (size == 0 && mApiCallCount == 0) {
+ return; // nothing to write.
+ }
+
+ out.startTag(null, TAG_ROOT);
+
+ ShortcutService.writeAttr(out, ATTR_NAME, mPackageName);
+ ShortcutService.writeAttr(out, ATTR_DYNAMIC_COUNT, mDynamicShortcutCount);
+ ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount);
+ ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime);
+
+ for (int j = 0; j < size; j++) {
+ saveShortcut(out, mShortcuts.valueAt(j));
+ }
+
+ out.endTag(null, TAG_ROOT);
+ }
+
+ private static void saveShortcut(XmlSerializer out, ShortcutInfo si)
+ throws IOException, XmlPullParserException {
+ out.startTag(null, TAG_SHORTCUT);
+ ShortcutService.writeAttr(out, ATTR_ID, si.getId());
+ // writeAttr(out, "package", si.getPackageName()); // not needed
+ ShortcutService.writeAttr(out, ATTR_ACTIVITY, si.getActivityComponent());
+ // writeAttr(out, "icon", si.getIcon()); // We don't save it.
+ ShortcutService.writeAttr(out, ATTR_TITLE, si.getTitle());
+ ShortcutService.writeAttr(out, ATTR_INTENT, si.getIntentNoExtras());
+ ShortcutService.writeAttr(out, ATTR_WEIGHT, si.getWeight());
+ ShortcutService.writeAttr(out, ATTR_TIMESTAMP,
+ si.getLastChangedTimestamp());
+ ShortcutService.writeAttr(out, ATTR_FLAGS, si.getFlags());
+ ShortcutService.writeAttr(out, ATTR_ICON_RES, si.getIconResourceId());
+ ShortcutService.writeAttr(out, ATTR_BITMAP_PATH, si.getBitmapPath());
+
+ ShortcutService.writeTagExtra(out, TAG_INTENT_EXTRAS,
+ si.getIntentPersistableExtras());
+ ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras());
+
+ out.endTag(null, TAG_SHORTCUT);
+ }
+
+ public static ShortcutPackage loadFromXml(XmlPullParser parser, int userId)
+ throws IOException, XmlPullParserException {
+
+ final String packageName = ShortcutService.parseStringAttribute(parser,
+ ATTR_NAME);
+
+ final ShortcutPackage ret = new ShortcutPackage(userId, packageName);
+
+ ret.mDynamicShortcutCount =
+ ShortcutService.parseIntAttribute(parser, ATTR_DYNAMIC_COUNT);
+ ret.mApiCallCount =
+ ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT);
+ ret.mLastResetTime =
+ ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET);
+
+ final int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+ final int depth = parser.getDepth();
+ final String tag = parser.getName();
+ switch (tag) {
+ case TAG_SHORTCUT:
+ final ShortcutInfo si = parseShortcut(parser, packageName);
+
+ // Don't use addShortcut(), we don't need to save the icon.
+ ret.mShortcuts.put(si.getId(), si);
+ continue;
+ }
+ throw ShortcutService.throwForInvalidTag(depth, tag);
+ }
+ return ret;
+ }
+
+ private static ShortcutInfo parseShortcut(XmlPullParser parser, String packageName)
+ throws IOException, XmlPullParserException {
+ String id;
+ ComponentName activityComponent;
+ // Icon icon;
+ String title;
+ Intent intent;
+ PersistableBundle intentPersistableExtras = null;
+ int weight;
+ PersistableBundle extras = null;
+ long lastChangedTimestamp;
+ int flags;
+ int iconRes;
+ String bitmapPath;
+
+ id = ShortcutService.parseStringAttribute(parser, ATTR_ID);
+ activityComponent = ShortcutService.parseComponentNameAttribute(parser,
+ ATTR_ACTIVITY);
+ title = ShortcutService.parseStringAttribute(parser, ATTR_TITLE);
+ intent = ShortcutService.parseIntentAttribute(parser, ATTR_INTENT);
+ weight = (int) ShortcutService.parseLongAttribute(parser, ATTR_WEIGHT);
+ lastChangedTimestamp = (int) ShortcutService.parseLongAttribute(parser,
+ ATTR_TIMESTAMP);
+ flags = (int) ShortcutService.parseLongAttribute(parser, ATTR_FLAGS);
+ iconRes = (int) ShortcutService.parseLongAttribute(parser, ATTR_ICON_RES);
+ bitmapPath = ShortcutService.parseStringAttribute(parser, ATTR_BITMAP_PATH);
+
+ final int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+ final int depth = parser.getDepth();
+ final String tag = parser.getName();
+ if (ShortcutService.DEBUG_LOAD) {
+ Slog.d(TAG, String.format(" depth=%d type=%d name=%s",
+ depth, type, tag));
+ }
+ switch (tag) {
+ case TAG_INTENT_EXTRAS:
+ intentPersistableExtras = PersistableBundle.restoreFromXml(parser);
+ continue;
+ case TAG_EXTRAS:
+ extras = PersistableBundle.restoreFromXml(parser);
+ continue;
+ }
+ throw ShortcutService.throwForInvalidTag(depth, tag);
+ }
+ return new ShortcutInfo(
+ id, packageName, activityComponent, /* icon =*/ null, title, intent,
+ intentPersistableExtras, weight, extras, lastChangedTimestamp, flags,
+ iconRes, bitmapPath);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index e831bb1..42954f5 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -53,6 +53,7 @@
import android.os.SELinux;
import android.os.ShellCommand;
import android.os.UserHandle;
+import android.os.UserManager;
import android.text.TextUtils;
import android.text.format.Formatter;
import android.text.format.Time;
@@ -67,6 +68,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.content.PackageMonitor;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.Preconditions;
@@ -99,6 +101,11 @@
*
* - Default launcher check does take a few ms. Worth caching.
*
+ * - Allow non-default launcher to start pinned shortcuts. (but not dynamic.)
+ *
+ * - Extract the user/package/launcher classes to their own files. Maybe rename so they all have
+ * the same "Shortcut" prefix.
+ *
* - Listen to PACKAGE_*, remove orphan info, update timestamp for icon res
* -> Need to scan all packages when a user starts too.
* -> Clear data -> remove all dynamic? but not the pinned?
@@ -215,7 +222,7 @@
* User ID -> UserShortcuts
*/
@GuardedBy("mLock")
- private final SparseArray<UserShortcuts> mUsers = new SparseArray<>();
+ private final SparseArray<ShortcutUser> mUsers = new SparseArray<>();
/**
* Max number of dynamic shortcuts that each application can have at a time.
@@ -243,6 +250,7 @@
private int mSaveDelayMillis;
private final PackageManagerInternal mPackageManagerInternal;
+ private final UserManager mUserManager;
@GuardedBy("mLock")
private List<Integer> mDirtyUserIds = new ArrayList<>();
@@ -257,6 +265,9 @@
LocalServices.addService(ShortcutServiceInternal.class, new LocalService());
mHandler = new Handler(looper);
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+ mUserManager = context.getSystemService(UserManager.class);
+
+ mPackageMonitor.register(context, looper, UserHandle.ALL, /* externalStorage= */ false);
}
/**
@@ -282,16 +293,12 @@
@Override
public void onCleanupUser(int userHandle) {
- synchronized (mService.mLock) {
- mService.onCleanupUserLocked(userHandle);
- }
+ mService.handleCleanupUser(userHandle);
}
@Override
public void onUnlockUser(int userId) {
- synchronized (mService.mLock) {
- mService.onStartUserLocked(userId);
- }
+ mService.handleUnlockUser(userId);
}
}
@@ -308,13 +315,24 @@
}
/** lifecycle event */
- void onStartUserLocked(int userId) {
- // Preload
- getUserShortcutsLocked(userId);
+ void handleUnlockUser(int userId) {
+ synchronized (mLock) {
+ // Preload
+ getUserShortcutsLocked(userId);
+ }
}
/** lifecycle event */
- void onCleanupUserLocked(int userId) {
+ void handleCleanupUser(int userId) {
+ synchronized (mLock) {
+ unloadUserLocked(userId);
+ }
+ }
+
+ private void unloadUserLocked(int userId) {
+ if (DEBUG) {
+ Slog.d(TAG, "unloadUserLocked: user=" + userId);
+ }
// Save all dirty information.
saveDirtyInfo();
@@ -615,7 +633,7 @@
}
@Nullable
- private UserShortcuts loadUserLocked(@UserIdInt int userId) {
+ private ShortcutUser loadUserLocked(@UserIdInt int userId) {
final File path = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES);
if (DEBUG) {
Slog.d(TAG, "Loading from " + path);
@@ -631,7 +649,7 @@
}
return null;
}
- UserShortcuts ret = null;
+ ShortcutUser ret = null;
try {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(in, StandardCharsets.UTF_8.name());
@@ -648,8 +666,8 @@
Slog.d(TAG, String.format("depth=%d type=%d name=%s",
depth, type, tag));
}
- if ((depth == 1) && UserShortcuts.TAG_ROOT.equals(tag)) {
- ret = UserShortcuts.loadFromXml(parser, userId);
+ if ((depth == 1) && ShortcutUser.TAG_ROOT.equals(tag)) {
+ ret = ShortcutUser.loadFromXml(parser, userId);
continue;
}
throwForInvalidTag(depth, tag);
@@ -752,15 +770,21 @@
}
}
+ @GuardedBy("mLock")
+ @NonNull
+ boolean isUserLoadedLocked(@UserIdInt int userId) {
+ return mUsers.get(userId) != null;
+ }
+
/** Return the per-user state. */
@GuardedBy("mLock")
@NonNull
- UserShortcuts getUserShortcutsLocked(@UserIdInt int userId) {
- UserShortcuts userPackages = mUsers.get(userId);
+ ShortcutUser getUserShortcutsLocked(@UserIdInt int userId) {
+ ShortcutUser userPackages = mUsers.get(userId);
if (userPackages == null) {
userPackages = loadUserLocked(userId);
if (userPackages == null) {
- userPackages = new UserShortcuts(userId);
+ userPackages = new ShortcutUser(userId);
}
mUsers.put(userId, userPackages);
}
@@ -770,14 +794,14 @@
/** Return the per-user per-package state. */
@GuardedBy("mLock")
@NonNull
- PackageShortcuts getPackageShortcutsLocked(
+ ShortcutPackage getPackageShortcutsLocked(
@NonNull String packageName, @UserIdInt int userId) {
return getUserShortcutsLocked(userId).getPackageShortcuts(packageName);
}
@GuardedBy("mLock")
@NonNull
- LauncherShortcuts getLauncherShortcuts(
+ ShortcutLauncher getLauncherShortcuts(
@NonNull String packageName, @UserIdInt int userId) {
return getUserShortcutsLocked(userId).getLauncherShortcuts(packageName);
}
@@ -1047,6 +1071,9 @@
}
private void notifyListeners(@NonNull String packageName, @UserIdInt int userId) {
+ if (!mUserManager.isUserRunning(userId)) {
+ return;
+ }
postToHandler(() -> {
final ArrayList<ShortcutChangeListener> copy;
synchronized (mLock) {
@@ -1142,7 +1169,7 @@
final int size = newShortcuts.size();
synchronized (mLock) {
- final PackageShortcuts ps = getPackageShortcutsLocked(packageName, userId);
+ final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
// Throttling.
if (!ps.tryApiCall(this)) {
@@ -1177,7 +1204,7 @@
final int size = newShortcuts.size();
synchronized (mLock) {
- final PackageShortcuts ps = getPackageShortcutsLocked(packageName, userId);
+ final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
// Throttling.
if (!ps.tryApiCall(this)) {
@@ -1214,7 +1241,7 @@
verifyCaller(packageName, userId);
synchronized (mLock) {
- final PackageShortcuts ps = getPackageShortcutsLocked(packageName, userId);
+ final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
// Throttling.
if (!ps.tryApiCall(this)) {
@@ -1354,7 +1381,7 @@
start = System.currentTimeMillis();
}
- final UserShortcuts user = getUserShortcutsLocked(userId);
+ final ShortcutUser user = getUserShortcutsLocked(userId);
final List<ResolveInfo> allHomeCandidates = new ArrayList<>();
@@ -1420,6 +1447,44 @@
}
}
+ // === House keeping ===
+
+ @VisibleForTesting
+ void cleanUpPackageLocked(String packageName, int userId) {
+ final boolean wasUserLoaded = isUserLoadedLocked(userId);
+
+ final ShortcutUser mUser = getUserShortcutsLocked(userId);
+ boolean doNotify = false;
+
+ // First, remove the package from the package list (if the package is a publisher).
+ if (mUser.getPackages().remove(packageName) != null) {
+ doNotify = true;
+ }
+ // Also remove from the launcher list (if the package is a launcher).
+ mUser.getLaunchers().remove(packageName);
+
+ // Then remove pinned shortcuts from all launchers.
+ for (int i = mUser.getLaunchers().size() - 1; i >= 0; i--) {
+ mUser.getLaunchers().valueAt(i).cleanUpPackage(packageName);
+ }
+ // Now there may be orphan shortcuts because we removed pinned shortucts at the previous
+ // step. Remove them too.
+ for (int i = mUser.getPackages().size() - 1; i >= 0; i--) {
+ mUser.getPackages().valueAt(i).refreshPinnedFlags(this);
+ }
+
+ scheduleSaveUser(userId);
+
+ if (doNotify) {
+ notifyListeners(packageName, userId);
+ }
+
+ if (!wasUserLoaded) {
+ // Note this will execute the scheduled save.
+ unloadUserLocked(userId);
+ }
+ }
+
/**
* Entry point from {@link LauncherApps}.
*/
@@ -1441,7 +1506,7 @@
callingPackage, packageName, changedSince,
componentName, queryFlags, userId, ret, cloneFlag);
} else {
- final ArrayMap<String, PackageShortcuts> packages =
+ final ArrayMap<String, ShortcutPackage> packages =
getUserShortcutsLocked(userId).getPackages();
for (int i = packages.size() - 1; i >= 0; i--) {
getShortcutsInnerLocked(
@@ -1575,6 +1640,50 @@
}
}
+ private PackageMonitor mPackageMonitor = new PackageMonitor() {
+ @Override
+ public void onPackageUpdateFinished(String packageName, int uid) {
+ handlePackageUpdateFinished(packageName, getChangingUserId());
+ }
+
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ handlePackageRemoved(packageName, getChangingUserId());
+ }
+
+ @Override
+ public void onPackageRemovedAllUsers(String packageName, int uid) {
+ handlePackageRemovedAllUsers(packageName, getChangingUserId());
+ }
+ };
+
+ void handlePackageUpdateFinished(String packageName, @UserIdInt int userId) {
+ if (DEBUG) {
+ Slog.d(TAG, "onPackageUpdateFinished() userId=" + userId);
+ }
+ // TODO Update the version.
+ }
+
+ void handlePackageRemoved(String packageName, @UserIdInt int userId) {
+ if (DEBUG) {
+ Slog.d(TAG, "onPackageRemoved() userId=" + userId);
+ }
+ synchronized (mLock) {
+ cleanUpPackageLocked(packageName, userId);
+ }
+ }
+
+ void handlePackageRemovedAllUsers(String packageName, @UserIdInt int userId) {
+ if (DEBUG) {
+ Slog.d(TAG, "onPackageRemovedAllUsers() userId=" + userId);
+ }
+ synchronized (mLock) {
+ cleanUpPackageLocked(packageName, userId);
+ }
+
+ // TODO Remove from all users, which we can't if the user is locked.
+ }
+
// === Dump ===
@Override
@@ -1823,25 +1932,29 @@
// === Unit test support ===
// Injection point.
+ @VisibleForTesting
long injectCurrentTimeMillis() {
return System.currentTimeMillis();
}
// Injection point.
+ @VisibleForTesting
int injectBinderCallingUid() {
return getCallingUid();
}
- final int getCallingUserId() {
+ private int getCallingUserId() {
return UserHandle.getUserId(injectBinderCallingUid());
}
// Injection point.
+ @VisibleForTesting
long injectClearCallingIdentity() {
return Binder.clearCallingIdentity();
}
// Injection point.
+ @VisibleForTesting
void injectRestoreCallingIdentity(long token) {
Binder.restoreCallingIdentity(token);
}
@@ -1854,10 +1967,12 @@
Slog.wtf(TAG, message, e);
}
+ @VisibleForTesting
File injectSystemDataPath() {
return Environment.getDataSystemDirectory();
}
+ @VisibleForTesting
File injectUserDataPath(@UserIdInt int userId) {
return new File(Environment.getDataSystemCeDirectory(userId), DIRECTORY_PER_USER);
}
@@ -1867,16 +1982,18 @@
return ActivityManager.isLowRamDeviceStatic();
}
+ @VisibleForTesting
PackageManagerInternal injectPackageManagerInternal() {
return mPackageManagerInternal;
}
+ @VisibleForTesting
File getUserBitmapFilePath(@UserIdInt int userId) {
return new File(injectUserDataPath(userId), DIRECTORY_BITMAPS);
}
@VisibleForTesting
- SparseArray<UserShortcuts> getShortcutsForTest() {
+ SparseArray<ShortcutUser> getShortcutsForTest() {
return mUsers;
}
@@ -1913,790 +2030,13 @@
@VisibleForTesting
ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) {
synchronized (mLock) {
- return getPackageShortcutsLocked(packageName, userId).findShortcutById(shortcutId);
+ final ShortcutUser user = mUsers.get(userId);
+ if (user == null) return null;
+
+ final ShortcutPackage pkg = user.getPackages().get(packageName);
+ if (pkg == null) return null;
+
+ return pkg.findShortcutById(shortcutId);
}
}
}
-
-/**
- * Per-user information.
- */
-class UserShortcuts {
- private static final String TAG = ShortcutService.TAG;
-
- static final String TAG_ROOT = "user";
- private static final String TAG_LAUNCHER = "launcher";
-
- private static final String ATTR_VALUE = "value";
-
- @UserIdInt
- final int mUserId;
-
- private final ArrayMap<String, PackageShortcuts> mPackages = new ArrayMap<>();
-
- private final ArrayMap<String, LauncherShortcuts> mLaunchers = new ArrayMap<>();
-
- private ComponentName mLauncherComponent;
-
- public UserShortcuts(int userId) {
- mUserId = userId;
- }
-
- public ArrayMap<String, PackageShortcuts> getPackages() {
- return mPackages;
- }
-
- public ArrayMap<String, LauncherShortcuts> getLaunchers() {
- return mLaunchers;
- }
-
- public PackageShortcuts getPackageShortcuts(@NonNull String packageName) {
- PackageShortcuts ret = mPackages.get(packageName);
- if (ret == null) {
- ret = new PackageShortcuts(mUserId, packageName);
- mPackages.put(packageName, ret);
- }
- return ret;
- }
-
- public LauncherShortcuts getLauncherShortcuts(@NonNull String packageName) {
- LauncherShortcuts ret = mLaunchers.get(packageName);
- if (ret == null) {
- ret = new LauncherShortcuts(mUserId, packageName);
- mLaunchers.put(packageName, ret);
- }
- return ret;
- }
-
- public void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException {
- out.startTag(null, TAG_ROOT);
-
- ShortcutService.writeTagValue(out, TAG_LAUNCHER,
- mLauncherComponent);
-
- final int lsize = mLaunchers.size();
- for (int i = 0; i < lsize; i++) {
- mLaunchers.valueAt(i).saveToXml(out);
- }
-
- final int psize = mPackages.size();
- for (int i = 0; i < psize; i++) {
- mPackages.valueAt(i).saveToXml(out);
- }
-
- out.endTag(null, TAG_ROOT);
- }
-
- public static UserShortcuts loadFromXml(XmlPullParser parser, int userId)
- throws IOException, XmlPullParserException {
- final UserShortcuts ret = new UserShortcuts(userId);
-
- final int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type != XmlPullParser.START_TAG) {
- continue;
- }
- final int depth = parser.getDepth();
- final String tag = parser.getName();
- switch (tag) {
- case TAG_LAUNCHER: {
- ret.mLauncherComponent = ShortcutService.parseComponentNameAttribute(
- parser, ATTR_VALUE);
- continue;
- }
- case PackageShortcuts.TAG_ROOT: {
- final PackageShortcuts shortcuts = PackageShortcuts.loadFromXml(parser, userId);
-
- // Don't use addShortcut(), we don't need to save the icon.
- ret.getPackages().put(shortcuts.mPackageName, shortcuts);
- continue;
- }
-
- case LauncherShortcuts.TAG_ROOT: {
- final LauncherShortcuts shortcuts =
- LauncherShortcuts.loadFromXml(parser, userId);
-
- ret.getLaunchers().put(shortcuts.mPackageName, shortcuts);
- continue;
- }
- }
- throw ShortcutService.throwForInvalidTag(depth, tag);
- }
- return ret;
- }
-
- public ComponentName getLauncherComponent() {
- return mLauncherComponent;
- }
-
- public void setLauncherComponent(ShortcutService s, ComponentName launcherComponent) {
- if (Objects.equal(mLauncherComponent, launcherComponent)) {
- return;
- }
- mLauncherComponent = launcherComponent;
- s.scheduleSaveUser(mUserId);
- }
-
- public void resetThrottling() {
- for (int i = mPackages.size() - 1; i >= 0; i--) {
- mPackages.valueAt(i).resetThrottling();
- }
- }
-
- public void dump(@NonNull ShortcutService s, @NonNull PrintWriter pw, @NonNull String prefix) {
- pw.print(prefix);
- pw.print("User: ");
- pw.print(mUserId);
- pw.println();
-
- pw.print(prefix);
- pw.print(" ");
- pw.print("Default launcher: ");
- pw.print(mLauncherComponent);
- pw.println();
-
- for (int i = 0; i < mLaunchers.size(); i++) {
- mLaunchers.valueAt(i).dump(s, pw, prefix + " ");
- }
-
- for (int i = 0; i < mPackages.size(); i++) {
- mPackages.valueAt(i).dump(s, pw, prefix + " ");
- }
- }
-}
-
-class LauncherShortcuts {
- private static final String TAG = ShortcutService.TAG;
-
- static final String TAG_ROOT = "launcher-pins";
-
- private static final String TAG_PACKAGE = "package";
- private static final String TAG_PIN = "pin";
-
- private static final String ATTR_VALUE = "value";
- private static final String ATTR_PACKAGE_NAME = "package-name";
-
- @UserIdInt
- final int mUserId;
-
- @NonNull
- final String mPackageName;
-
- /**
- * Package name -> IDs.
- */
- final private ArrayMap<String, ArraySet<String>> mPinnedShortcuts = new ArrayMap<>();
-
- LauncherShortcuts(@UserIdInt int userId, @NonNull String packageName) {
- mUserId = userId;
- mPackageName = packageName;
- }
-
- public void pinShortcuts(@NonNull ShortcutService s, @NonNull String packageName,
- @NonNull List<String> ids) {
- final int idSize = ids.size();
- if (idSize == 0) {
- mPinnedShortcuts.remove(packageName);
- } else {
- final ArraySet<String> prevSet = mPinnedShortcuts.get(packageName);
-
- // Pin shortcuts. Make sure only pin the ones that were visible to the caller.
- // i.e. a non-dynamic, pinned shortcut by *other launchers* shouldn't be pinned here.
-
- final PackageShortcuts packageShortcuts =
- s.getPackageShortcutsLocked(packageName, mUserId);
- final ArraySet<String> newSet = new ArraySet<>();
-
- for (int i = 0; i < idSize; i++) {
- final String id = ids.get(i);
- final ShortcutInfo si = packageShortcuts.findShortcutById(id);
- if (si == null) {
- continue;
- }
- if (si.isDynamic() || (prevSet != null && prevSet.contains(id))) {
- newSet.add(id);
- }
- }
- mPinnedShortcuts.put(packageName, newSet);
- }
- s.getPackageShortcutsLocked(packageName, mUserId).refreshPinnedFlags(s);
- }
-
- /**
- * Return the pinned shortcut IDs for the publisher package.
- */
- public ArraySet<String> getPinnedShortcutIds(@NonNull String packageName) {
- return mPinnedShortcuts.get(packageName);
- }
-
- /**
- * Persist.
- */
- public void saveToXml(XmlSerializer out) throws IOException {
- out.startTag(null, TAG_ROOT);
- ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME,
- mPackageName);
-
- final int size = mPinnedShortcuts.size();
- for (int i = 0; i < size; i++) {
- out.startTag(null, TAG_PACKAGE);
- ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME,
- mPinnedShortcuts.keyAt(i));
-
- final ArraySet<String> ids = mPinnedShortcuts.valueAt(i);
- final int idSize = ids.size();
- for (int j = 0; j < idSize; j++) {
- ShortcutService.writeTagValue(out, TAG_PIN, ids.valueAt(j));
- }
- out.endTag(null, TAG_PACKAGE);
- }
-
- out.endTag(null, TAG_ROOT);
- }
-
- /**
- * Load.
- */
- public static LauncherShortcuts loadFromXml(XmlPullParser parser, int userId)
- throws IOException, XmlPullParserException {
- final String launcherPackageName = ShortcutService.parseStringAttribute(parser,
- ATTR_PACKAGE_NAME);
-
- final LauncherShortcuts ret = new LauncherShortcuts(userId, launcherPackageName);
-
- ArraySet<String> ids = null;
- final int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type != XmlPullParser.START_TAG) {
- continue;
- }
- final int depth = parser.getDepth();
- final String tag = parser.getName();
- switch (tag) {
- case TAG_PACKAGE: {
- final String packageName = ShortcutService.parseStringAttribute(parser,
- ATTR_PACKAGE_NAME);
- ids = new ArraySet<>();
- ret.mPinnedShortcuts.put(packageName, ids);
- continue;
- }
- case TAG_PIN: {
- ids.add(ShortcutService.parseStringAttribute(parser,
- ATTR_VALUE));
- continue;
- }
- }
- throw ShortcutService.throwForInvalidTag(depth, tag);
- }
- return ret;
- }
-
- public void dump(@NonNull ShortcutService s, @NonNull PrintWriter pw, @NonNull String prefix) {
- pw.println();
-
- pw.print(prefix);
- pw.print("Launcher: ");
- pw.print(mPackageName);
- pw.println();
-
- final int size = mPinnedShortcuts.size();
- for (int i = 0; i < size; i++) {
- pw.println();
-
- pw.print(prefix);
- pw.print(" ");
- pw.print("Package: ");
- pw.println(mPinnedShortcuts.keyAt(i));
-
- final ArraySet<String> ids = mPinnedShortcuts.valueAt(i);
- final int idSize = ids.size();
-
- for (int j = 0; j < idSize; j++) {
- pw.print(prefix);
- pw.print(" ");
- pw.print(ids.valueAt(j));
- pw.println();
- }
- }
- }
-}
-
-/**
- * All the information relevant to shortcuts from a single package (per-user).
- */
-class PackageShortcuts {
- private static final String TAG = ShortcutService.TAG;
-
- static final String TAG_ROOT = "package";
- private static final String TAG_INTENT_EXTRAS = "intent-extras";
- private static final String TAG_EXTRAS = "extras";
- private static final String TAG_SHORTCUT = "shortcut";
-
- private static final String ATTR_NAME = "name";
- private static final String ATTR_DYNAMIC_COUNT = "dynamic-count";
- private static final String ATTR_CALL_COUNT = "call-count";
- private static final String ATTR_LAST_RESET = "last-reset";
- private static final String ATTR_ID = "id";
- private static final String ATTR_ACTIVITY = "activity";
- private static final String ATTR_TITLE = "title";
- private static final String ATTR_INTENT = "intent";
- private static final String ATTR_WEIGHT = "weight";
- private static final String ATTR_TIMESTAMP = "timestamp";
- private static final String ATTR_FLAGS = "flags";
- private static final String ATTR_ICON_RES = "icon-res";
- private static final String ATTR_BITMAP_PATH = "bitmap-path";
-
- @UserIdInt
- final int mUserId;
-
- @NonNull
- final String mPackageName;
-
- /**
- * All the shortcuts from the package, keyed on IDs.
- */
- final private ArrayMap<String, ShortcutInfo> mShortcuts = new ArrayMap<>();
-
- /**
- * # of dynamic shortcuts.
- */
- private int mDynamicShortcutCount = 0;
-
- /**
- * # of times the package has called rate-limited APIs.
- */
- private int mApiCallCount;
-
- /**
- * When {@link #mApiCallCount} was reset last time.
- */
- private long mLastResetTime;
-
- PackageShortcuts(int userId, String packageName) {
- mUserId = userId;
- mPackageName = packageName;
- }
-
- @Nullable
- public ShortcutInfo findShortcutById(String id) {
- return mShortcuts.get(id);
- }
-
- private ShortcutInfo deleteShortcut(@NonNull ShortcutService s,
- @NonNull String id) {
- final ShortcutInfo shortcut = mShortcuts.remove(id);
- if (shortcut != null) {
- s.removeIcon(mUserId, shortcut);
- shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED);
- }
- return shortcut;
- }
-
- void addShortcut(@NonNull ShortcutService s, @NonNull ShortcutInfo newShortcut) {
- deleteShortcut(s, newShortcut.getId());
- s.saveIconAndFixUpShortcut(mUserId, newShortcut);
- mShortcuts.put(newShortcut.getId(), newShortcut);
- }
-
- /**
- * Add a shortcut, or update one with the same ID, with taking over existing flags.
- *
- * It checks the max number of dynamic shortcuts.
- */
- public void addDynamicShortcut(@NonNull ShortcutService s,
- @NonNull ShortcutInfo newShortcut) {
- newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC);
-
- final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId());
-
- final boolean wasPinned;
- final int newDynamicCount;
-
- if (oldShortcut == null) {
- wasPinned = false;
- newDynamicCount = mDynamicShortcutCount + 1; // adding a dynamic shortcut.
- } else {
- wasPinned = oldShortcut.isPinned();
- if (oldShortcut.isDynamic()) {
- newDynamicCount = mDynamicShortcutCount; // not adding a dynamic shortcut.
- } else {
- newDynamicCount = mDynamicShortcutCount + 1; // adding a dynamic shortcut.
- }
- }
-
- // Make sure there's still room.
- s.enforceMaxDynamicShortcuts(newDynamicCount);
-
- // Okay, make it dynamic and add.
- if (wasPinned) {
- newShortcut.addFlags(ShortcutInfo.FLAG_PINNED);
- }
-
- addShortcut(s, newShortcut);
- mDynamicShortcutCount = newDynamicCount;
- }
-
- /**
- * Remove all shortcuts that aren't pinned nor dynamic.
- */
- private void removeOrphans(@NonNull ShortcutService s) {
- ArrayList<String> removeList = null; // Lazily initialize.
-
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
-
- if (si.isPinned() || si.isDynamic()) continue;
-
- if (removeList == null) {
- removeList = new ArrayList<>();
- }
- removeList.add(si.getId());
- }
- if (removeList != null) {
- for (int i = removeList.size() - 1 ; i >= 0; i--) {
- deleteShortcut(s, removeList.get(i));
- }
- }
- }
-
- /**
- * Remove all dynamic shortcuts.
- */
- public void deleteAllDynamicShortcuts(@NonNull ShortcutService s) {
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- mShortcuts.valueAt(i).clearFlags(ShortcutInfo.FLAG_DYNAMIC);
- }
- removeOrphans(s);
- mDynamicShortcutCount = 0;
- }
-
- /**
- * Remove a dynamic shortcut by ID.
- */
- public void deleteDynamicWithId(@NonNull ShortcutService s, @NonNull String shortcutId) {
- final ShortcutInfo oldShortcut = mShortcuts.get(shortcutId);
-
- if (oldShortcut == null) {
- return;
- }
- if (oldShortcut.isDynamic()) {
- mDynamicShortcutCount--;
- }
- if (oldShortcut.isPinned()) {
- oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
- } else {
- deleteShortcut(s, shortcutId);
- }
- }
-
- /**
- * Called after a launcher updates the pinned set. For each shortcut in this package,
- * set FLAG_PINNED if any launcher has pinned it. Otherwise, clear it.
- *
- * <p>Then remove all shortcuts that are not dynamic and no longer pinned either.
- */
- public void refreshPinnedFlags(@NonNull ShortcutService s) {
- // First, un-pin all shortcuts
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- mShortcuts.valueAt(i).clearFlags(ShortcutInfo.FLAG_PINNED);
- }
-
- // Then, for the pinned set for each launcher, set the pin flag one by one.
- final ArrayMap<String, LauncherShortcuts> launchers =
- s.getUserShortcutsLocked(mUserId).getLaunchers();
-
- for (int l = launchers.size() - 1; l >= 0; l--) {
- final LauncherShortcuts launcherShortcuts = launchers.valueAt(l);
- final ArraySet<String> pinned = launcherShortcuts.getPinnedShortcutIds(mPackageName);
-
- if (pinned == null || pinned.size() == 0) {
- continue;
- }
- for (int i = pinned.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.get(pinned.valueAt(i));
- if (si == null) {
- s.wtf("Shortcut not found");
- } else {
- si.addFlags(ShortcutInfo.FLAG_PINNED);
- }
- }
- }
-
- // Lastly, remove the ones that are no longer pinned nor dynamic.
- removeOrphans(s);
- }
-
- /**
- * Number of calls that the caller has made, since the last reset.
- */
- public int getApiCallCount(@NonNull ShortcutService s) {
- final long last = s.getLastResetTimeLocked();
-
- final long now = s.injectCurrentTimeMillis();
- if (ShortcutService.isClockValid(now) && mLastResetTime > now) {
- Slog.w(TAG, "Clock rewound");
- // Clock rewound.
- mLastResetTime = now;
- mApiCallCount = 0;
- return mApiCallCount;
- }
-
- // If not reset yet, then reset.
- if (mLastResetTime < last) {
- if (ShortcutService.DEBUG) {
- Slog.d(TAG, String.format("My last reset=%d, now=%d, last=%d: resetting",
- mLastResetTime, now, last));
- }
- mApiCallCount = 0;
- mLastResetTime = last;
- }
- return mApiCallCount;
- }
-
- /**
- * If the caller app hasn't been throttled yet, increment {@link #mApiCallCount}
- * and return true. Otherwise just return false.
- */
- public boolean tryApiCall(@NonNull ShortcutService s) {
- if (getApiCallCount(s) >= s.mMaxDailyUpdates) {
- return false;
- }
- mApiCallCount++;
- return true;
- }
-
- public void resetRateLimitingForCommandLine() {
- mApiCallCount = 0;
- mLastResetTime = 0;
- }
-
- /**
- * Find all shortcuts that match {@code query}.
- */
- public void findAll(@NonNull ShortcutService s, @NonNull List<ShortcutInfo> result,
- @Nullable Predicate<ShortcutInfo> query, int cloneFlag,
- @Nullable String callingLauncher) {
-
- // Set of pinned shortcuts by the calling launcher.
- final ArraySet<String> pinnedByCallerSet = (callingLauncher == null) ? null
- : s.getLauncherShortcuts(callingLauncher, mUserId)
- .getPinnedShortcutIds(mPackageName);
-
- for (int i = 0; i < mShortcuts.size(); i++) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
-
- // If it's called by non-launcher (i.e. publisher, always include -> true.
- // Otherwise, only include non-dynamic pinned one, if the calling launcher has pinned
- // it.
- final boolean isPinnedByCaller = (callingLauncher == null)
- || ((pinnedByCallerSet != null) && pinnedByCallerSet.contains(si.getId()));
- if (!si.isDynamic()) {
- if (!si.isPinned()) {
- s.wtf("Shortcut not pinned here");
- continue;
- }
- if (!isPinnedByCaller) {
- continue;
- }
- }
- final ShortcutInfo clone = si.clone(cloneFlag);
- // Fix up isPinned for the caller. Note we need to do it before the "test" callback,
- // since it may check isPinned.
- if (!isPinnedByCaller) {
- clone.clearFlags(ShortcutInfo.FLAG_PINNED);
- }
- if (query == null || query.test(clone)) {
- result.add(clone);
- }
- }
- }
-
- public void resetThrottling() {
- mApiCallCount = 0;
- }
-
- public void dump(@NonNull ShortcutService s, @NonNull PrintWriter pw, @NonNull String prefix) {
- pw.println();
-
- pw.print(prefix);
- pw.print("Package: ");
- pw.print(mPackageName);
- pw.println();
-
- pw.print(prefix);
- pw.print(" ");
- pw.print("Calls: ");
- pw.print(getApiCallCount(s));
- pw.println();
-
- // This should be after getApiCallCount(), which may update it.
- pw.print(prefix);
- pw.print(" ");
- pw.print("Last reset: [");
- pw.print(mLastResetTime);
- pw.print("] ");
- pw.print(s.formatTime(mLastResetTime));
- pw.println();
-
- pw.println(" Shortcuts:");
- long totalBitmapSize = 0;
- final ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts;
- final int size = shortcuts.size();
- for (int i = 0; i < size; i++) {
- final ShortcutInfo si = shortcuts.valueAt(i);
- pw.print(" ");
- pw.println(si.toInsecureString());
- if (si.getBitmapPath() != null) {
- final long len = new File(si.getBitmapPath()).length();
- pw.print(" ");
- pw.print("bitmap size=");
- pw.println(len);
-
- totalBitmapSize += len;
- }
- }
- pw.print(prefix);
- pw.print(" ");
- pw.print("Total bitmap size: ");
- pw.print(totalBitmapSize);
- pw.print(" (");
- pw.print(Formatter.formatFileSize(s.mContext, totalBitmapSize));
- pw.println(")");
- }
-
- public void saveToXml(@NonNull XmlSerializer out) throws IOException, XmlPullParserException {
- out.startTag(null, TAG_ROOT);
-
- ShortcutService.writeAttr(out, ATTR_NAME, mPackageName);
- ShortcutService.writeAttr(out, ATTR_DYNAMIC_COUNT, mDynamicShortcutCount);
- ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount);
- ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime);
-
- final int size = mShortcuts.size();
- for (int j = 0; j < size; j++) {
- saveShortcut(out, mShortcuts.valueAt(j));
- }
-
- out.endTag(null, TAG_ROOT);
- }
-
- private static void saveShortcut(XmlSerializer out, ShortcutInfo si)
- throws IOException, XmlPullParserException {
- out.startTag(null, TAG_SHORTCUT);
- ShortcutService.writeAttr(out, ATTR_ID, si.getId());
- // writeAttr(out, "package", si.getPackageName()); // not needed
- ShortcutService.writeAttr(out, ATTR_ACTIVITY, si.getActivityComponent());
- // writeAttr(out, "icon", si.getIcon()); // We don't save it.
- ShortcutService.writeAttr(out, ATTR_TITLE, si.getTitle());
- ShortcutService.writeAttr(out, ATTR_INTENT, si.getIntentNoExtras());
- ShortcutService.writeAttr(out, ATTR_WEIGHT, si.getWeight());
- ShortcutService.writeAttr(out, ATTR_TIMESTAMP,
- si.getLastChangedTimestamp());
- ShortcutService.writeAttr(out, ATTR_FLAGS, si.getFlags());
- ShortcutService.writeAttr(out, ATTR_ICON_RES, si.getIconResourceId());
- ShortcutService.writeAttr(out, ATTR_BITMAP_PATH, si.getBitmapPath());
-
- ShortcutService.writeTagExtra(out, TAG_INTENT_EXTRAS,
- si.getIntentPersistableExtras());
- ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras());
-
- out.endTag(null, TAG_SHORTCUT);
- }
-
- public static PackageShortcuts loadFromXml(XmlPullParser parser, int userId)
- throws IOException, XmlPullParserException {
-
- final String packageName = ShortcutService.parseStringAttribute(parser,
- ATTR_NAME);
-
- final PackageShortcuts ret = new PackageShortcuts(userId, packageName);
-
- ret.mDynamicShortcutCount =
- ShortcutService.parseIntAttribute(parser, ATTR_DYNAMIC_COUNT);
- ret.mApiCallCount =
- ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT);
- ret.mLastResetTime =
- ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET);
-
- final int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type != XmlPullParser.START_TAG) {
- continue;
- }
- final int depth = parser.getDepth();
- final String tag = parser.getName();
- switch (tag) {
- case TAG_SHORTCUT:
- final ShortcutInfo si = parseShortcut(parser, packageName);
-
- // Don't use addShortcut(), we don't need to save the icon.
- ret.mShortcuts.put(si.getId(), si);
- continue;
- }
- throw ShortcutService.throwForInvalidTag(depth, tag);
- }
- return ret;
- }
-
- private static ShortcutInfo parseShortcut(XmlPullParser parser, String packageName)
- throws IOException, XmlPullParserException {
- String id;
- ComponentName activityComponent;
- // Icon icon;
- String title;
- Intent intent;
- PersistableBundle intentPersistableExtras = null;
- int weight;
- PersistableBundle extras = null;
- long lastChangedTimestamp;
- int flags;
- int iconRes;
- String bitmapPath;
-
- id = ShortcutService.parseStringAttribute(parser, ATTR_ID);
- activityComponent = ShortcutService.parseComponentNameAttribute(parser,
- ATTR_ACTIVITY);
- title = ShortcutService.parseStringAttribute(parser, ATTR_TITLE);
- intent = ShortcutService.parseIntentAttribute(parser, ATTR_INTENT);
- weight = (int) ShortcutService.parseLongAttribute(parser, ATTR_WEIGHT);
- lastChangedTimestamp = (int) ShortcutService.parseLongAttribute(parser,
- ATTR_TIMESTAMP);
- flags = (int) ShortcutService.parseLongAttribute(parser, ATTR_FLAGS);
- iconRes = (int) ShortcutService.parseLongAttribute(parser, ATTR_ICON_RES);
- bitmapPath = ShortcutService.parseStringAttribute(parser, ATTR_BITMAP_PATH);
-
- final int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type != XmlPullParser.START_TAG) {
- continue;
- }
- final int depth = parser.getDepth();
- final String tag = parser.getName();
- if (ShortcutService.DEBUG_LOAD) {
- Slog.d(TAG, String.format(" depth=%d type=%d name=%s",
- depth, type, tag));
- }
- switch (tag) {
- case TAG_INTENT_EXTRAS:
- intentPersistableExtras = PersistableBundle.restoreFromXml(parser);
- continue;
- case TAG_EXTRAS:
- extras = PersistableBundle.restoreFromXml(parser);
- continue;
- }
- throw ShortcutService.throwForInvalidTag(depth, tag);
- }
- return new ShortcutInfo(
- id, packageName, activityComponent, /* icon =*/ null, title, intent,
- intentPersistableExtras, weight, extras, lastChangedTimestamp, flags,
- iconRes, bitmapPath);
- }
-}
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
new file mode 100644
index 0000000..4a6b1e4
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -0,0 +1,179 @@
+/*
+ * 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.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.ComponentName;
+import android.util.ArrayMap;
+
+import libcore.util.Objects;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * User information used by {@link ShortcutService}.
+ */
+class ShortcutUser {
+ private static final String TAG = ShortcutService.TAG;
+
+ static final String TAG_ROOT = "user";
+ private static final String TAG_LAUNCHER = "launcher";
+
+ private static final String ATTR_VALUE = "value";
+
+ @UserIdInt
+ final int mUserId;
+
+ private final ArrayMap<String, ShortcutPackage> mPackages = new ArrayMap<>();
+
+ private final ArrayMap<String, ShortcutLauncher> mLaunchers = new ArrayMap<>();
+
+ private ComponentName mLauncherComponent;
+
+ public ShortcutUser(int userId) {
+ mUserId = userId;
+ }
+
+ public ArrayMap<String, ShortcutPackage> getPackages() {
+ return mPackages;
+ }
+
+ public ArrayMap<String, ShortcutLauncher> getLaunchers() {
+ return mLaunchers;
+ }
+
+ public ShortcutPackage getPackageShortcuts(@NonNull String packageName) {
+ ShortcutPackage ret = mPackages.get(packageName);
+ if (ret == null) {
+ ret = new ShortcutPackage(mUserId, packageName);
+ mPackages.put(packageName, ret);
+ }
+ return ret;
+ }
+
+ public ShortcutLauncher getLauncherShortcuts(@NonNull String packageName) {
+ ShortcutLauncher ret = mLaunchers.get(packageName);
+ if (ret == null) {
+ ret = new ShortcutLauncher(mUserId, packageName);
+ mLaunchers.put(packageName, ret);
+ }
+ return ret;
+ }
+
+ public void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException {
+ out.startTag(null, TAG_ROOT);
+
+ ShortcutService.writeTagValue(out, TAG_LAUNCHER,
+ mLauncherComponent);
+
+ final int lsize = mLaunchers.size();
+ for (int i = 0; i < lsize; i++) {
+ mLaunchers.valueAt(i).saveToXml(out);
+ }
+
+ final int psize = mPackages.size();
+ for (int i = 0; i < psize; i++) {
+ mPackages.valueAt(i).saveToXml(out);
+ }
+
+ out.endTag(null, TAG_ROOT);
+ }
+
+ public static ShortcutUser loadFromXml(XmlPullParser parser, int userId)
+ throws IOException, XmlPullParserException {
+ final ShortcutUser ret = new ShortcutUser(userId);
+
+ final int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+ final int depth = parser.getDepth();
+ final String tag = parser.getName();
+ switch (tag) {
+ case TAG_LAUNCHER: {
+ ret.mLauncherComponent = ShortcutService.parseComponentNameAttribute(
+ parser, ATTR_VALUE);
+ continue;
+ }
+ case ShortcutPackage.TAG_ROOT: {
+ final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml(parser, userId);
+
+ // Don't use addShortcut(), we don't need to save the icon.
+ ret.getPackages().put(shortcuts.mPackageName, shortcuts);
+ continue;
+ }
+
+ case ShortcutLauncher.TAG_ROOT: {
+ final ShortcutLauncher shortcuts =
+ ShortcutLauncher.loadFromXml(parser, userId);
+
+ ret.getLaunchers().put(shortcuts.mPackageName, shortcuts);
+ continue;
+ }
+ }
+ throw ShortcutService.throwForInvalidTag(depth, tag);
+ }
+ return ret;
+ }
+
+ public ComponentName getLauncherComponent() {
+ return mLauncherComponent;
+ }
+
+ public void setLauncherComponent(ShortcutService s, ComponentName launcherComponent) {
+ if (Objects.equal(mLauncherComponent, launcherComponent)) {
+ return;
+ }
+ mLauncherComponent = launcherComponent;
+ s.scheduleSaveUser(mUserId);
+ }
+
+ public void resetThrottling() {
+ for (int i = mPackages.size() - 1; i >= 0; i--) {
+ mPackages.valueAt(i).resetThrottling();
+ }
+ }
+
+ public void dump(@NonNull ShortcutService s, @NonNull PrintWriter pw, @NonNull String prefix) {
+ pw.print(prefix);
+ pw.print("User: ");
+ pw.print(mUserId);
+ pw.println();
+
+ pw.print(prefix);
+ pw.print(" ");
+ pw.print("Default launcher: ");
+ pw.print(mLauncherComponent);
+ pw.println();
+
+ for (int i = 0; i < mLaunchers.size(); i++) {
+ mLaunchers.valueAt(i).dump(s, pw, prefix + " ");
+ }
+
+ for (int i = 0; i < mPackages.size(); i++) {
+ mPackages.valueAt(i).dump(s, pw, prefix + " ");
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index a3622b5..715f1e5 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1,3 +1,4 @@
+
/*
* Copyright (C) 2011 The Android Open Source Project
*
@@ -650,18 +651,27 @@
@Override
public UserInfo getUserInfo(int userId) {
+ checkManageUsersPermission("query user");
+ synchronized (mUsersLock) {
+ return getUserInfoLU(userId);
+ }
+ }
+
+ @Override
+ public boolean isManagedProfile(int userId) {
int callingUserId = UserHandle.getCallingUserId();
if (callingUserId != userId && !hasManageUsersPermission()) {
synchronized (mPackagesLock) {
if (!isSameProfileGroupLP(callingUserId, userId)) {
throw new SecurityException(
- "You need MANAGE_USERS permission to: query users outside profile" +
- " group");
+ "You need MANAGE_USERS permission to: check if specified user a " +
+ "managed profile outside your profile group");
}
}
}
synchronized (mUsersLock) {
- return getUserInfoLU(userId);
+ UserInfo userInfo = getUserInfoLU(userId);
+ return userInfo != null && userInfo.isManagedProfile();
}
}
diff --git a/services/core/java/com/android/server/policy/ShortcutManager.java b/services/core/java/com/android/server/policy/ShortcutManager.java
index a47f250..a14c614 100644
--- a/services/core/java/com/android/server/policy/ShortcutManager.java
+++ b/services/core/java/com/android/server/policy/ShortcutManager.java
@@ -78,7 +78,7 @@
public Intent getIntent(KeyCharacterMap kcm, int keyCode, int metaState) {
ShortcutInfo shortcut = null;
- // If the Shift key is preesed, then search for the shift shortcuts.
+ // If the Shift key is pressed, then search for the shift shortcuts.
boolean isShiftOn = (metaState & KeyEvent.META_SHIFT_ON) == KeyEvent.META_SHIFT_ON;
SparseArray<ShortcutInfo> shortcutMap = isShiftOn ? mShiftShortcuts : mShortcuts;
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index cbd77d4..ff5a0f9 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -71,6 +71,8 @@
import com.android.server.am.BatteryStatsService;
import com.android.server.lights.Light;
import com.android.server.lights.LightsManager;
+import com.android.server.vr.VrManagerInternal;
+import com.android.server.vr.VrStateListener;
import libcore.util.Objects;
import java.io.FileDescriptor;
@@ -158,6 +160,7 @@
// Power hints defined in hardware/libhardware/include/hardware/power.h.
private static final int POWER_HINT_LOW_POWER = 5;
private static final int POWER_HINT_SUSTAINED_PERFORMANCE = 6;
+ private static final int POWER_HINT_VR_MODE = 7;
// Power features defined in hardware/libhardware/include/hardware/power.h.
private static final int POWER_FEATURE_DOUBLE_TAP_TO_WAKE = 1;
@@ -654,6 +657,7 @@
resolver.registerContentObserver(Settings.Secure.getUriFor(
Secure.BRIGHTNESS_USE_TWILIGHT),
false, mSettingsObserver, UserHandle.USER_ALL);
+ getLocalService(VrManagerInternal.class).registerListener(mVrStateListener);
// Go.
readConfigurationLocked();
updateSettingsLocked();
@@ -3002,6 +3006,13 @@
}
}
+ private final VrStateListener mVrStateListener = new VrStateListener() {
+ @Override
+ public void onVrStateChanged(boolean enabled) {
+ powerHintInternal(POWER_HINT_VR_MODE, enabled ? 1 : 0);
+ }
+ };
+
/**
* Handler for asynchronous operations performed by the power manager.
*/
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index 0aa3052..6bf949c 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -158,7 +158,7 @@
@Override
public void onBootPhase(int phase) {
- if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
+ if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
synchronized (mLock) {
Looper looper = Looper.getMainLooper();
Handler handler = new Handler(looper);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 3cf9590..fb9b1ce 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -20,6 +20,7 @@
import static android.app.WallpaperManager.FLAG_SET_LOCK;
import static android.os.ParcelFileDescriptor.*;
+import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.AppGlobals;
import android.app.AppOpsManager;
@@ -875,12 +876,8 @@
if (!isWallpaperSupported(callingPackage) || !isWallpaperSettingAllowed(callingPackage)) {
return;
}
- if (userId != UserHandle.getCallingUserId()) {
- // cross-user call
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
- "WallpaperManagerService");
- }
+ userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, false, true, "clearWallpaper", null);
synchronized (mLock) {
clearWallpaperLocked(false, which, userId, null);
@@ -1103,12 +1100,8 @@
@Override
public ParcelFileDescriptor getWallpaper(IWallpaperManagerCallback cb, final int which,
Bundle outParams, int wallpaperUserId) {
- if (wallpaperUserId != UserHandle.getCallingUserId()) {
- // cross-user call
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
- "WallpaperManagerService");
- }
+ wallpaperUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), wallpaperUserId, false, true, "getWallpaper", null);
if (which != FLAG_SET_SYSTEM && which != FLAG_SET_LOCK) {
throw new IllegalArgumentException("Must specify exactly one kind of wallpaper to read");
@@ -1147,6 +1140,7 @@
}
}
+ @Override
public WallpaperInfo getWallpaperInfo() {
int userId = UserHandle.getCallingUserId();
synchronized (mLock) {
@@ -1159,6 +1153,26 @@
}
@Override
+ public int getWallpaperIdForUser(int which, int userId) {
+ userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, false, true, "getWallpaperIdForUser", null);
+
+ if (which != FLAG_SET_SYSTEM && which != FLAG_SET_LOCK) {
+ throw new IllegalArgumentException("Must specify exactly one kind of wallpaper");
+ }
+
+ final SparseArray<WallpaperData> map =
+ (which == FLAG_SET_LOCK) ? mLockWallpaperMap : mWallpaperMap;
+ synchronized (mLock) {
+ WallpaperData wallpaper = map.get(userId);
+ if (wallpaper != null) {
+ return wallpaper.wallpaperId;
+ }
+ }
+ return -1;
+ }
+
+ @Override
public boolean setLockWallpaperCallback(IWallpaperManagerCallback cb) {
checkPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW);
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/wm/BoundsAnimationController.java b/services/core/java/com/android/server/wm/BoundsAnimationController.java
index 79d3d84..b7d6062 100644
--- a/services/core/java/com/android/server/wm/BoundsAnimationController.java
+++ b/services/core/java/com/android/server/wm/BoundsAnimationController.java
@@ -214,7 +214,7 @@
void getFullScreenBounds(Rect bounds);
}
- void animateBounds(final AnimateBoundsUser target, Rect from, Rect to) {
+ void animateBounds(final AnimateBoundsUser target, Rect from, Rect to, int animationDuration) {
boolean moveToFullscreen = false;
if (to == null) {
to = new Rect();
@@ -242,7 +242,8 @@
new BoundsAnimator(target, from, to, moveToFullscreen, replacing);
mRunningAnimations.put(target, animator);
animator.setFloatValues(0f, 1f);
- animator.setDuration(DEFAULT_APP_TRANSITION_DURATION * DEBUG_ANIMATION_SLOW_DOWN_FACTOR);
+ animator.setDuration((animationDuration != -1 ? animationDuration
+ : DEFAULT_APP_TRANSITION_DURATION) * DEBUG_ANIMATION_SLOW_DOWN_FACTOR);
animator.setInterpolator(new LinearInterpolator());
animator.start();
}
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index a589f89..c0c1ed8 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -195,8 +195,10 @@
@Override
public void repositionChild(IWindow window, int left, int top, int right, int bottom,
- long deferTransactionUntilFrame, Rect outFrame) {
+ int requestedWidth, int requestedHeight,
+ long deferTransactionUntilFrame, Rect outFrame) {
mService.repositionChild(this, window, left, top, right, bottom,
+ requestedWidth, requestedHeight,
deferTransactionUntilFrame, outFrame);
}
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 60b2e4a..c667767 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -1089,7 +1089,7 @@
}
}
try {
- mService.mActivityManager.resizeStack(mStackId, bounds, false, true, false);
+ mService.mActivityManager.resizeStack(mStackId, bounds, false, true, false, -1);
} catch (RemoteException e) {
}
return true;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 607a3e9..14291ca 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2522,6 +2522,7 @@
void repositionChild(Session session, IWindow client,
int left, int top, int right, int bottom,
+ int requestedWidth, int requestedHeight,
long deferTransactionUntilFrame, Rect outFrame) {
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "repositionChild");
long origId = Binder.clearCallingIdentity();
@@ -2537,6 +2538,7 @@
"repositionChild called but window is not"
+ "attached to a parent win=" + win);
}
+ win.setRequestedSize(requestedWidth, requestedHeight);
win.mAttrs.x = left;
win.mAttrs.y = top;
@@ -2593,7 +2595,8 @@
== PackageManager.PERMISSION_GRANTED;
long origId = Binder.clearCallingIdentity();
-
+ final boolean preserveGeometry = (attrs != null) && (attrs.privateFlags &
+ WindowManager.LayoutParams.PRIVATE_FLAG_PRESERVE_GEOMETRY) != 0;
synchronized(mWindowMap) {
WindowState win = windowForClientLocked(session, client, false);
if (win == null) {
@@ -2601,7 +2604,7 @@
}
WindowStateAnimator winAnimator = win.mWinAnimator;
- if (viewVisibility != View.GONE) {
+ if (!preserveGeometry && viewVisibility != View.GONE) {
win.setRequestedSize(requestedWidth, requestedHeight);
}
@@ -2650,7 +2653,9 @@
if ((attrChanges & WindowManager.LayoutParams.ALPHA_CHANGED) != 0) {
winAnimator.mAlpha = attrs.alpha;
}
- win.setWindowScale(requestedWidth, requestedHeight);
+ if (!preserveGeometry) {
+ win.setWindowScale(win.mRequestedWidth, win.mRequestedHeight);
+ }
boolean imMayMove = (flagChanges & (FLAG_ALT_FOCUSABLE_IM | FLAG_NOT_FOCUSABLE)) != 0;
final boolean isDefaultDisplay = win.isDefaultDisplay();
@@ -8241,8 +8246,8 @@
break;
case RESIZE_STACK: {
try {
- mActivityManager.resizeStack(msg.arg1, (Rect) msg.obj, msg.arg2 == 1, false,
- false);
+ mActivityManager.resizeStack(
+ msg.arg1, (Rect) msg.obj, msg.arg2 == 1, false, false, -1);
} catch (RemoteException e) {
// This will not happen since we are in the same process.
}
@@ -10460,7 +10465,7 @@
}
}
- public void animateResizePinnedStack(final Rect bounds) {
+ public void animateResizePinnedStack(final Rect bounds, final int animationDuration) {
synchronized (mWindowMap) {
final TaskStack stack = mStackIdToStack.get(PINNED_STACK_ID);
if (stack == null) {
@@ -10472,7 +10477,8 @@
UiThread.getHandler().post(new Runnable() {
@Override
public void run() {
- mBoundsAnimationController.animateBounds(stack, originalBounds, bounds);
+ mBoundsAnimationController.animateBounds(
+ stack, originalBounds, bounds, animationDuration);
}
});
}
diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
index 34f2e2e..8e11511 100644
--- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
@@ -1008,29 +1008,41 @@
private class TestNetworkCallback extends NetworkCallback {
private final ConditionVariable mConditionVariable = new ConditionVariable();
private CallbackState mLastCallback = CallbackState.NONE;
+ private Network mLastNetwork;
public void onAvailable(Network network) {
assertEquals(CallbackState.NONE, mLastCallback);
mLastCallback = CallbackState.AVAILABLE;
+ mLastNetwork = network;
mConditionVariable.open();
}
public void onLosing(Network network, int maxMsToLive) {
assertEquals(CallbackState.NONE, mLastCallback);
mLastCallback = CallbackState.LOSING;
+ mLastNetwork = network;
mConditionVariable.open();
}
public void onLost(Network network) {
assertEquals(CallbackState.NONE, mLastCallback);
mLastCallback = CallbackState.LOST;
+ mLastNetwork = network;
mConditionVariable.open();
}
void expectCallback(CallbackState state) {
+ expectCallback(state, null);
+ }
+
+ void expectCallback(CallbackState state, MockNetworkAgent mockAgent) {
waitFor(mConditionVariable);
assertEquals(state, mLastCallback);
+ if (mockAgent != null) {
+ assertEquals(mockAgent.getNetwork(), mLastNetwork);
+ }
mLastCallback = CallbackState.NONE;
+ mLastNetwork = null;
mConditionVariable.close();
}
@@ -1389,6 +1401,55 @@
execptionCalled);
}
+ @LargeTest
+ public void testRegisterDefaultNetworkCallback() throws Exception {
+ final TestNetworkCallback defaultNetworkCallback = new TestNetworkCallback();
+ mCm.registerDefaultNetworkCallback(defaultNetworkCallback);
+ defaultNetworkCallback.assertNoCallback();
+
+ // Create a TRANSPORT_CELLULAR request to keep the mobile interface up
+ // whenever Wi-Fi is up. Without this, the mobile network agent is
+ // reaped before any other activity can take place.
+ final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback();
+ final NetworkRequest cellRequest = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_CELLULAR).build();
+ mCm.requestNetwork(cellRequest, cellNetworkCallback);
+ cellNetworkCallback.assertNoCallback();
+
+ // Bring up cell and expect CALLBACK_AVAILABLE.
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+ cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ defaultNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+
+ // Bring up wifi and expect CALLBACK_AVAILABLE.
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(true);
+ cellNetworkCallback.assertNoCallback();
+ defaultNetworkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+
+ // Bring down cell. Expect no default network callback, since it wasn't the default.
+ mCellNetworkAgent.disconnect();
+ cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+ defaultNetworkCallback.assertNoCallback();
+
+ // Bring up cell. Expect no default network callback, since it won't be the default.
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+ cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ defaultNetworkCallback.assertNoCallback();
+
+ // Bring down wifi. Expect the default network callback to notified of LOST wifi
+ // followed by AVAILABLE cell.
+ mWiFiNetworkAgent.disconnect();
+ cellNetworkCallback.assertNoCallback();
+ defaultNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ defaultNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ mCellNetworkAgent.disconnect();
+ cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+ defaultNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+ }
+
private static class TestKeepaliveCallback extends PacketKeepaliveCallback {
public static enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR };
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
index ad86fd0..28966ca 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
@@ -22,6 +22,7 @@
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
@@ -56,6 +57,7 @@
import android.test.InstrumentationTestCase;
import android.test.mock.MockContext;
import android.test.suitebuilder.annotation.SmallTest;
+import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
@@ -124,6 +126,11 @@
}
@Override
+ public String getSystemServiceName(Class<?> serviceClass) {
+ return getTestContext().getSystemServiceName(serviceClass);
+ }
+
+ @Override
public PackageManager getPackageManager() {
return mMockPackageManager;
}
@@ -231,7 +238,7 @@
@Override
boolean injectIsLowRamDevice() {
- return mInjectdIsLowRamDevice;
+ return mInjectedIsLowRamDevice;
}
@Override
@@ -343,7 +350,7 @@
private long mInjectedCurrentTimeLillis;
- private boolean mInjectdIsLowRamDevice;
+ private boolean mInjectedIsLowRamDevice;
private int mInjectedCallingUid;
private String mInjectedClientPackage;
@@ -657,6 +664,14 @@
return new ComponentName(mClientContext, clazz);
}
+ private <T> Set<T> makeSet(T... values) {
+ final HashSet<T> ret = new HashSet<>();
+ for (T s : values) {
+ ret.add(s);
+ }
+ return ret;
+ }
+
@NonNull
private ShortcutInfo findById(List<ShortcutInfo> list, String id) {
for (ShortcutInfo s : list) {
@@ -841,6 +856,14 @@
return mService.getPackageShortcutForTest(packageName, shortcutId, userId);
}
+ private void assertShortcutExists(String packageName, String shortcutId, int userId) {
+ assertTrue(getPackageShortcut(packageName, shortcutId, userId) != null);
+ }
+
+ private void assertShortcutNotExists(String packageName, String shortcutId, int userId) {
+ assertTrue(getPackageShortcut(packageName, shortcutId, userId) == null);
+ }
+
private ShortcutInfo getPackageShortcut(String packageName, String shortcutId) {
return getPackageShortcut(packageName, shortcutId, getCallingUserId());
}
@@ -849,6 +872,27 @@
return getPackageShortcut(getCallingPackage(), shortcutId, getCallingUserId());
}
+ private List<ShortcutInfo> getLauncherShortcuts(String launcher, int userId, int queryFlags) {
+ final List<ShortcutInfo>[] ret = new List[1];
+ runWithCaller(launcher, userId, () -> {
+ final ShortcutQuery q = new ShortcutQuery();
+ q.setQueryFlags(queryFlags);
+ ret[0] = mLauncherApps.getShortcuts(q, UserHandle.of(userId));
+ });
+ return ret[0];
+ }
+
+ private List<ShortcutInfo> getLauncherPinnedShortcuts(String launcher, int userId) {
+ return getLauncherShortcuts(launcher, userId, ShortcutQuery.FLAG_GET_PINNED);
+ }
+
+ /**
+ * Wrap a set in an ArraySet just to get a better toString.
+ */
+ private <T> Set<T> set(Set<T> in) {
+ return new ArraySet<T>(in);
+ }
+
/**
* Test for the first launch path, no settings file available.
*/
@@ -857,7 +901,8 @@
}
/**
- * Test for {@link ShortcutService#updateTimes()}
+ * Test for {@link ShortcutService#getLastResetTimeLocked()} and
+ * {@link ShortcutService#getNextResetTimeLocked()}.
*/
public void testUpdateAndGetNextResetTimeLocked() {
assertResetTimes(START_TIME, START_TIME + INTERVAL);
@@ -928,7 +973,7 @@
assertEquals(CompressFormat.WEBP, mService.getIconPersistFormatForTest());
assertEquals(75, mService.getIconPersistQualityForTest());
- mInjectdIsLowRamDevice = true;
+ mInjectedIsLowRamDevice = true;
mService.updateConfigurationLocked(
ConfigConstants.KEY_MAX_ICON_DIMENSION_DP + "=100,"
+ ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM + "=50,"
@@ -2205,6 +2250,8 @@
// TODO Add "multi" version -- run the test with two launchers and make sure the callback
// argument only contains the ones that are actually visible to each launcher.
+ when(mMockUserManager.isUserRunning(eq(USER_0))).thenReturn(true);
+
LauncherApps.Callback c0 = mock(LauncherApps.Callback.class);
// Set listeners
@@ -2320,6 +2367,20 @@
eq(UserHandle.of(USER_0))
);
assertEquals(0, shortcuts.getValue().size());
+
+ // Remove CALLING_PACKAGE_2
+ reset(c0);
+ mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_0);
+
+ // Should get a callback with an empty list.
+ waitOnMainThread();
+ shortcuts = ArgumentCaptor.forClass(List.class);
+ verify(c0).onShortcutsChanged(
+ eq(CALLING_PACKAGE_2),
+ shortcuts.capture(),
+ eq(UserHandle.of(USER_0))
+ );
+ assertEquals(0, shortcuts.getValue().size());
}
// === Test for persisting ===
@@ -2436,7 +2497,7 @@
assertEquals(0, mService.getShortcutsForTest().size());
// this will pre-load the per-user info.
- mService.onStartUserLocked(UserHandle.USER_SYSTEM);
+ mService.handleUnlockUser(UserHandle.USER_SYSTEM);
// Now it's loaded.
assertEquals(1, mService.getShortcutsForTest().size());
@@ -2462,7 +2523,7 @@
.getLauncherComponent().getPackageName());
// Start another user
- mService.onStartUserLocked(USER_10);
+ mService.handleUnlockUser(USER_10);
// Now the size is 2.
assertEquals(2, mService.getShortcutsForTest().size());
@@ -2478,7 +2539,7 @@
assertNull(mService.getShortcutsForTest().get(USER_10).getLauncherComponent());
// Try stopping the user
- mService.onCleanupUserLocked(USER_10);
+ mService.handleCleanupUser(USER_10);
// Now it's unloaded.
assertEquals(1, mService.getShortcutsForTest().size());
@@ -2486,6 +2547,248 @@
// TODO Check all other fields
}
+ public void testCleanupPackage() {
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(Arrays.asList(
+ makeShortcut("s0_1"))));
+ });
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(Arrays.asList(
+ makeShortcut("s0_2"))));
+ });
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, Arrays.asList("s0_1"),
+ UserHandle.of(USER_0));
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, Arrays.asList("s0_2"),
+ UserHandle.of(USER_0));
+ });
+ runWithCaller(LAUNCHER_2, USER_0, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, Arrays.asList("s0_1"),
+ UserHandle.of(USER_0));
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, Arrays.asList("s0_2"),
+ UserHandle.of(USER_0));
+ });
+
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertTrue(mManager.setDynamicShortcuts(Arrays.asList(
+ makeShortcut("s10_1"))));
+ });
+ runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
+ assertTrue(mManager.setDynamicShortcuts(Arrays.asList(
+ makeShortcut("s10_2"))));
+ });
+ runWithCaller(LAUNCHER_1, USER_10, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, Arrays.asList("s10_1"),
+ UserHandle.of(USER_10));
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, Arrays.asList("s10_2"),
+ UserHandle.of(USER_10));
+ });
+ runWithCaller(LAUNCHER_2, USER_10, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, Arrays.asList("s10_1"),
+ UserHandle.of(USER_10));
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, Arrays.asList("s10_2"),
+ UserHandle.of(USER_10));
+ });
+
+ // Remove all dynamic shortcuts; now all shortcuts are just pinned.
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ mManager.deleteAllDynamicShortcuts();
+ });
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ mManager.deleteAllDynamicShortcuts();
+ });
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ mManager.deleteAllDynamicShortcuts();
+ });
+ runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
+ mManager.deleteAllDynamicShortcuts();
+ });
+
+
+ final SparseArray<ShortcutUser> users = mService.getShortcutsForTest();
+ assertEquals(2, users.size());
+ assertEquals(USER_0, users.keyAt(0));
+ assertEquals(USER_10, users.keyAt(1));
+
+ final ShortcutUser user0 = users.get(USER_0);
+ final ShortcutUser user10 = users.get(USER_10);
+
+
+ // Check the registered packages.
+
+ assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
+ set(user0.getPackages().keySet()));
+ assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
+ set(user10.getPackages().keySet()));
+ assertEquals(makeSet(LAUNCHER_1, LAUNCHER_2),
+ set(user0.getLaunchers().keySet()));
+ assertEquals(makeSet(LAUNCHER_1, LAUNCHER_2),
+ set(user10.getLaunchers().keySet()));
+ assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
+ "s0_1", "s0_2");
+ assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
+ "s0_1", "s0_2");
+ assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_10),
+ "s10_1", "s10_2");
+ assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_10),
+ "s10_1", "s10_2");
+ assertShortcutExists(CALLING_PACKAGE_1, "s0_1", USER_0);
+ assertShortcutExists(CALLING_PACKAGE_2, "s0_2", USER_0);
+ assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10);
+ assertShortcutExists(CALLING_PACKAGE_2, "s10_2", USER_10);
+
+ mService.saveDirtyInfo();
+
+ // Nonexistent package.
+ mService.cleanUpPackageLocked("abc", USER_0);
+
+ // No changes.
+ assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
+ set(user0.getPackages().keySet()));
+ assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
+ set(user10.getPackages().keySet()));
+ assertEquals(makeSet(LAUNCHER_1, LAUNCHER_2),
+ set(user0.getLaunchers().keySet()));
+ assertEquals(makeSet(LAUNCHER_1, LAUNCHER_2),
+ set(user10.getLaunchers().keySet()));
+ assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
+ "s0_1", "s0_2");
+ assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
+ "s0_1", "s0_2");
+ assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_10),
+ "s10_1", "s10_2");
+ assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_10),
+ "s10_1", "s10_2");
+ assertShortcutExists(CALLING_PACKAGE_1, "s0_1", USER_0);
+ assertShortcutExists(CALLING_PACKAGE_2, "s0_2", USER_0);
+ assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10);
+ assertShortcutExists(CALLING_PACKAGE_2, "s10_2", USER_10);
+
+ mService.saveDirtyInfo();
+
+ // Remove a package.
+ mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_0);
+
+ assertEquals(makeSet(CALLING_PACKAGE_2),
+ set(user0.getPackages().keySet()));
+ assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
+ set(user10.getPackages().keySet()));
+ assertEquals(makeSet(LAUNCHER_1, LAUNCHER_2),
+ set(user0.getLaunchers().keySet()));
+ assertEquals(makeSet(LAUNCHER_1, LAUNCHER_2),
+ set(user10.getLaunchers().keySet()));
+ assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
+ "s0_2");
+ assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
+ "s0_2");
+ assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_10),
+ "s10_1", "s10_2");
+ assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_10),
+ "s10_1", "s10_2");
+ assertShortcutNotExists(CALLING_PACKAGE_1, "s0_1", USER_0);
+ assertShortcutExists(CALLING_PACKAGE_2, "s0_2", USER_0);
+ assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10);
+ assertShortcutExists(CALLING_PACKAGE_2, "s10_2", USER_10);
+
+ mService.saveDirtyInfo();
+
+ // Remove a launcher.
+ mService.cleanUpPackageLocked(LAUNCHER_1, USER_10);
+
+ assertEquals(makeSet(CALLING_PACKAGE_2),
+ set(user0.getPackages().keySet()));
+ assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
+ set(user10.getPackages().keySet()));
+ assertEquals(makeSet(LAUNCHER_1, LAUNCHER_2),
+ set(user0.getLaunchers().keySet()));
+ assertEquals(makeSet(LAUNCHER_2),
+ set(user10.getLaunchers().keySet()));
+ assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
+ "s0_2");
+ assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
+ "s0_2");
+ assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_10),
+ "s10_1", "s10_2");
+ assertShortcutNotExists(CALLING_PACKAGE_1, "s0_1", USER_0);
+ assertShortcutExists(CALLING_PACKAGE_2, "s0_2", USER_0);
+ assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10);
+ assertShortcutExists(CALLING_PACKAGE_2, "s10_2", USER_10);
+
+ mService.saveDirtyInfo();
+
+ // Remove a package.
+ mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_10);
+
+ assertEquals(makeSet(CALLING_PACKAGE_2),
+ set(user0.getPackages().keySet()));
+ assertEquals(makeSet(CALLING_PACKAGE_1),
+ set(user10.getPackages().keySet()));
+ assertEquals(makeSet(LAUNCHER_1, LAUNCHER_2),
+ set(user0.getLaunchers().keySet()));
+ assertEquals(makeSet(LAUNCHER_2),
+ set(user10.getLaunchers().keySet()));
+ assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
+ "s0_2");
+ assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
+ "s0_2");
+ assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_10),
+ "s10_1");
+ assertShortcutNotExists(CALLING_PACKAGE_1, "s0_1", USER_0);
+ assertShortcutExists(CALLING_PACKAGE_2, "s0_2", USER_0);
+ assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10);
+ assertShortcutNotExists(CALLING_PACKAGE_2, "s10_2", USER_10);
+
+ mService.saveDirtyInfo();
+
+ // Remove the other launcher from user 10 too.
+ mService.cleanUpPackageLocked(LAUNCHER_2, USER_10);
+
+ assertEquals(makeSet(CALLING_PACKAGE_2),
+ set(user0.getPackages().keySet()));
+ assertEquals(makeSet(CALLING_PACKAGE_1),
+ set(user10.getPackages().keySet()));
+ assertEquals(makeSet(LAUNCHER_1, LAUNCHER_2),
+ set(user0.getLaunchers().keySet()));
+ assertEquals(makeSet(),
+ set(user10.getLaunchers().keySet()));
+ assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
+ "s0_2");
+ assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
+ "s0_2");
+
+ // Note the pinned shortcuts on user-10 no longer referred, so they should both be removed.
+ assertShortcutNotExists(CALLING_PACKAGE_1, "s0_1", USER_0);
+ assertShortcutExists(CALLING_PACKAGE_2, "s0_2", USER_0);
+ assertShortcutNotExists(CALLING_PACKAGE_1, "s10_1", USER_10);
+ assertShortcutNotExists(CALLING_PACKAGE_2, "s10_2", USER_10);
+
+ mService.saveDirtyInfo();
+
+ // More remove.
+ mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_10);
+
+ assertEquals(makeSet(CALLING_PACKAGE_2),
+ set(user0.getPackages().keySet()));
+ assertEquals(makeSet(),
+ set(user10.getPackages().keySet()));
+ assertEquals(makeSet(LAUNCHER_1, LAUNCHER_2),
+ set(user0.getLaunchers().keySet()));
+ assertEquals(makeSet(),
+ set(user10.getLaunchers().keySet()));
+ assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
+ "s0_2");
+ assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
+ "s0_2");
+
+ // Note the pinned shortcuts on user-10 no longer referred, so they should both be removed.
+ assertShortcutNotExists(CALLING_PACKAGE_1, "s0_1", USER_0);
+ assertShortcutExists(CALLING_PACKAGE_2, "s0_2", USER_0);
+ assertShortcutNotExists(CALLING_PACKAGE_1, "s10_1", USER_10);
+ assertShortcutNotExists(CALLING_PACKAGE_2, "s10_2", USER_10);
+
+ mService.saveDirtyInfo();
+ }
+
// TODO Detailed test for hasShortcutPermissionInner().
// TODO Add tests for the command line functions too.
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 35a0464..5fe944c 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -608,6 +608,7 @@
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
| Intent.FLAG_RECEIVER_FOREGROUND);
intent.putExtra(UsbManager.USB_CONNECTED, mConnected);
+ intent.putExtra(UsbManager.USB_HOST_CONNECTED, mHostConnected);
intent.putExtra(UsbManager.USB_CONFIGURED, mConfigured);
intent.putExtra(UsbManager.USB_DATA_UNLOCKED, isUsbTransferAllowed() && mUsbDataUnlocked);
@@ -717,6 +718,9 @@
case MSG_UPDATE_HOST_STATE:
mHostConnected = (msg.arg1 == 1);
updateUsbNotification();
+ if (mBootCompleted) {
+ updateUsbStateBroadcastIfNeeded();
+ }
break;
case MSG_ENABLE_ADB:
setAdbEnabled(msg.arg1 == 1);
diff --git a/telecomm/java/android/telecom/ParcelableConnection.java b/telecomm/java/android/telecom/ParcelableConnection.java
index fe0a4d8..ce51c96 100644
--- a/telecomm/java/android/telecom/ParcelableConnection.java
+++ b/telecomm/java/android/telecom/ParcelableConnection.java
@@ -188,7 +188,7 @@
DisconnectCause disconnectCause = source.readParcelable(classLoader);
List<String> conferenceableConnectionIds = new ArrayList<>();
source.readStringList(conferenceableConnectionIds);
- Bundle extras = source.readBundle(classLoader);
+ Bundle extras = Bundle.setDefusable(source.readBundle(classLoader), true);
return new ParcelableConnection(
phoneAccount,
diff --git a/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java
index 1ca94dc..ff3f19f 100644
--- a/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java
@@ -48,7 +48,7 @@
// ---- delegate data ----
- private final int mSrcColor;
+ private final java.awt.Color mSrcColor;
private final Mode mMode;
@@ -66,9 +66,9 @@
@Override
public void applyFilter(Graphics2D g, int width, int height) {
- BufferedImage image = createFilterImage(width, height);
g.setComposite(getComposite(mMode, 0xFF));
- g.drawImage(image, 0, 0, null);
+ g.setColor(mSrcColor);
+ g.fillRect(0, 0, width, height);
}
// ---- native methods ----
@@ -84,22 +84,10 @@
// ---- Private delegate/helper methods ----
private PorterDuffColorFilter_Delegate(int srcColor, int mode) {
- mSrcColor = srcColor;
+ mSrcColor = new java.awt.Color(srcColor, true /* hasAlpha */);
mMode = getCompatibleMode(getPorterDuffMode(mode));
}
- private BufferedImage createFilterImage(int width, int height) {
- BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
- Graphics2D graphics = image.createGraphics();
- try {
- graphics.setColor(new java.awt.Color(mSrcColor, true /* hasAlpha */));
- graphics.fillRect(0, 0, width, height);
- } finally {
- graphics.dispose();
- }
- return image;
- }
-
// For filtering the colors, the src image should contain the "color" only for pixel values
// which are not transparent in the target image. But, we are using a simple rectangular image
// completely filled with color. Hence some Composite rules do not apply as intended. However,
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 fe05b0e..53adb41 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
@@ -96,7 +96,8 @@
}
@Override
- public void repositionChild(IWindow childWindow, int x, int y, int width, int height,
+ public void repositionChild(IWindow window, int left, int top, int right, int bottom,
+ int requestedWidth, int requestedHeight,
long deferTransactionUntilFrame, Rect outFrame) {
// pass for now.
return;
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java
index b98f96f..bd17a2f 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java
@@ -16,6 +16,7 @@
package com.android.layoutlib.bridge.android;
+import com.android.ide.common.rendering.api.LayoutlibCallback;
import com.android.ide.common.rendering.api.RenderParams;
import com.android.ide.common.rendering.api.SessionParams.Key;
@@ -42,11 +43,16 @@
public static final Key<Boolean> FLAG_KEY_RECYCLER_VIEW_SUPPORT =
new Key<Boolean>("recyclerViewSupport", Boolean.class);
/**
- * The application package name. Used via
- * {@link com.android.ide.common.rendering.api.LayoutlibCallback#getFlag(Key)}
+ * The application package name. Used via {@link LayoutlibCallback#getFlag(Key)}
*/
public static final Key<String> FLAG_KEY_APPLICATION_PACKAGE =
new Key<String>("applicationPackage", String.class);
+ /**
+ * To tell LayoutLib that IDE supports providing XML Parser for a file (useful for getting in
+ * memory contents of the file). Used via {@link LayoutlibCallback#getFlag(Key)}
+ */
+ public static final Key<Boolean> FLAG_KEY_XML_FILE_PARSER_SUPPORT =
+ new Key<Boolean>("xmlFileParser", Boolean.class);
// Disallow instances.
private RenderParamsFlags() {}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
index 494b3d2..a21de56 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
@@ -25,6 +25,7 @@
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
+import com.android.layoutlib.bridge.android.RenderParamsFlags;
import com.android.ninepatch.NinePatch;
import com.android.ninepatch.NinePatchChunk;
import com.android.resources.Density;
@@ -142,8 +143,13 @@
return null;
}
+ XmlPullParser parser = null;
// first check if the value is a file (xml most likely)
- XmlPullParser parser = context.getLayoutlibCallback().getXmlFileParser(value);
+ Boolean psiParserSupport = context.getLayoutlibCallback().getFlag(
+ RenderParamsFlags.FLAG_KEY_XML_FILE_PARSER_SUPPORT);
+ if (psiParserSupport != null && psiParserSupport) {
+ parser = context.getLayoutlibCallback().getXmlFileParser(value);
+ }
if (parser == null) {
File f = new File(value);
if (f.isFile()) {
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index 58e8761..9e15d60 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -1047,7 +1047,7 @@
StringBuffer sb = new StringBuffer();
for (String key : mFields.keySet()) {
// Don't display password in toString().
- String value = (key == PASSWORD_KEY) ? "<removed>" : mFields.get(key);
+ String value = PASSWORD_KEY.equals(key) ? "<removed>" : mFields.get(key);
sb.append(key).append(" ").append(value).append("\n");
}
return sb.toString();
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 823fd26..c040b06 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -689,7 +689,6 @@
mContext = context;
mService = service;
mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
- init();
}
/**
@@ -1478,8 +1477,7 @@
* @hide for CTS test only
*/
public void getTxPacketCount(TxPacketCountListener listener) {
- validateChannel();
- mAsyncChannel.sendMessage(RSSI_PKTCNT_FETCH, 0, putListener(listener));
+ getChannel().sendMessage(RSSI_PKTCNT_FETCH, 0, putListener(listener));
}
/**
@@ -1972,30 +1970,28 @@
}
}
- private void init() {
- Messenger messenger = getWifiServiceMessenger();
- if (messenger == null) {
- mAsyncChannel = null;
- return;
+ private synchronized AsyncChannel getChannel() {
+ if (mAsyncChannel == null) {
+ Messenger messenger = getWifiServiceMessenger();
+ if (messenger == null) {
+ throw new IllegalStateException(
+ "getWifiServiceMessenger() returned null! This is invalid.");
+ }
+
+ mHandlerThread = new HandlerThread("WifiManager");
+ mAsyncChannel = new AsyncChannel();
+ mConnected = new CountDownLatch(1);
+
+ mHandlerThread.start();
+ Handler handler = new ServiceHandler(mHandlerThread.getLooper());
+ mAsyncChannel.connect(mContext, handler, messenger);
+ try {
+ mConnected.await();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "interrupted wait at init");
+ }
}
-
- mHandlerThread = new HandlerThread("WifiManager");
- mAsyncChannel = new AsyncChannel();
- mConnected = new CountDownLatch(1);
-
- mHandlerThread.start();
- Handler handler = new ServiceHandler(mHandlerThread.getLooper());
- mAsyncChannel.connect(mContext, handler, messenger);
- try {
- mConnected.await();
- } catch (InterruptedException e) {
- Log.e(TAG, "interrupted wait at init");
- }
- }
-
- private void validateChannel() {
- if (mAsyncChannel == null) throw new IllegalStateException(
- "No permission to access and change wifi or a bad initialization");
+ return mAsyncChannel;
}
/**
@@ -2016,10 +2012,9 @@
*/
public void connect(WifiConfiguration config, ActionListener listener) {
if (config == null) throw new IllegalArgumentException("config cannot be null");
- validateChannel();
// Use INVALID_NETWORK_ID for arg1 when passing a config object
// arg1 is used to pass network id when the network already exists
- mAsyncChannel.sendMessage(CONNECT_NETWORK, WifiConfiguration.INVALID_NETWORK_ID,
+ getChannel().sendMessage(CONNECT_NETWORK, WifiConfiguration.INVALID_NETWORK_ID,
putListener(listener), config);
}
@@ -2038,8 +2033,7 @@
*/
public void connect(int networkId, ActionListener listener) {
if (networkId < 0) throw new IllegalArgumentException("Network id cannot be negative");
- validateChannel();
- mAsyncChannel.sendMessage(CONNECT_NETWORK, networkId, putListener(listener));
+ getChannel().sendMessage(CONNECT_NETWORK, networkId, putListener(listener));
}
/**
@@ -2062,8 +2056,7 @@
*/
public void save(WifiConfiguration config, ActionListener listener) {
if (config == null) throw new IllegalArgumentException("config cannot be null");
- validateChannel();
- mAsyncChannel.sendMessage(SAVE_NETWORK, 0, putListener(listener), config);
+ getChannel().sendMessage(SAVE_NETWORK, 0, putListener(listener), config);
}
/**
@@ -2081,8 +2074,7 @@
*/
public void forget(int netId, ActionListener listener) {
if (netId < 0) throw new IllegalArgumentException("Network id cannot be negative");
- validateChannel();
- mAsyncChannel.sendMessage(FORGET_NETWORK, netId, putListener(listener));
+ getChannel().sendMessage(FORGET_NETWORK, netId, putListener(listener));
}
/**
@@ -2096,8 +2088,7 @@
*/
public void disable(int netId, ActionListener listener) {
if (netId < 0) throw new IllegalArgumentException("Network id cannot be negative");
- validateChannel();
- mAsyncChannel.sendMessage(DISABLE_NETWORK, netId, putListener(listener));
+ getChannel().sendMessage(DISABLE_NETWORK, netId, putListener(listener));
}
/**
@@ -2125,8 +2116,7 @@
*/
public void startWps(WpsInfo config, WpsCallback listener) {
if (config == null) throw new IllegalArgumentException("config cannot be null");
- validateChannel();
- mAsyncChannel.sendMessage(START_WPS, 0, putListener(listener), config);
+ getChannel().sendMessage(START_WPS, 0, putListener(listener), config);
}
/**
@@ -2137,8 +2127,7 @@
* initialized again
*/
public void cancelWps(WpsCallback listener) {
- validateChannel();
- mAsyncChannel.sendMessage(CANCEL_WPS, 0, putListener(listener));
+ getChannel().sendMessage(CANCEL_WPS, 0, putListener(listener));
}
/**
@@ -2153,8 +2142,6 @@
return mService.getWifiServiceMessenger();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
- } catch (SecurityException e) {
- return null;
}
}