Merge "Revert "Fix NPE for invalid target exception""
diff --git a/Android.mk b/Android.mk
index c3e679b..6ec434c 100644
--- a/Android.mk
+++ b/Android.mk
@@ -520,7 +520,7 @@
frameworks/base/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.aidl \
frameworks/base/wifi/java/android/net/wifi/WpsInfo.aidl \
frameworks/base/wifi/java/android/net/wifi/ScanResult.aidl \
- frameworks/base/wifi/java/android/net/wifi/ScanInfo.aidl \
+ frameworks/base/wifi/java/android/net/wifi/PasspointManagementObjectDefinition.aidl \
frameworks/base/wifi/java/android/net/wifi/WifiEnterpriseConfig.aidl \
frameworks/base/wifi/java/android/net/wifi/WifiConfiguration.aidl \
frameworks/base/wifi/java/android/net/wifi/WifiInfo.aidl \
@@ -1193,6 +1193,11 @@
LOCAL_DX_FLAGS := --core-library
+ifneq ($(INCREMENTAL_BUILDS),)
+ LOCAL_PROGUARD_ENABLED := disabled
+ LOCAL_JACK_ENABLED := incremental
+endif
+
include $(BUILD_JAVA_LIBRARY)
diff --git a/api/current.txt b/api/current.txt
index ba73ca2..5361d0a 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -306,6 +306,7 @@
field public static final int baselineAlignBottom = 16843042; // 0x1010122
field public static final int baselineAligned = 16843046; // 0x1010126
field public static final int baselineAlignedChildIndex = 16843047; // 0x1010127
+ field public static final int bitmap = 16844055; // 0x1010517
field public static final int borderlessButtonStyle = 16843563; // 0x101032b
field public static final int bottom = 16843184; // 0x10101b0
field public static final int bottomBright = 16842957; // 0x10100cd
@@ -647,6 +648,8 @@
field public static final int horizontalScrollViewStyle = 16843603; // 0x1010353
field public static final int horizontalSpacing = 16843028; // 0x1010114
field public static final int host = 16842792; // 0x1010028
+ field public static final int hotSpotX = 16844056; // 0x1010518
+ field public static final int hotSpotY = 16844057; // 0x1010519
field public static final int hyphenationFrequency = 16843998; // 0x10104de
field public static final int icon = 16842754; // 0x1010002
field public static final int iconPreview = 16843337; // 0x1010249
@@ -6320,6 +6323,22 @@
field public static final android.os.Parcelable.Creator<android.app.usage.ConfigurationStats> CREATOR;
}
+ public class DataUsagePolicy {
+ field public final int networkType;
+ field public final java.lang.String[] subscriberIds;
+ field public final long thresholdInBytes;
+ field public final int[] uids;
+ }
+
+ public static class DataUsagePolicy.Builder {
+ ctor public DataUsagePolicy.Builder();
+ method public android.app.usage.DataUsagePolicy.Builder addSubscriberId(java.lang.String);
+ method public android.app.usage.DataUsagePolicy.Builder addUid(int);
+ method public android.app.usage.DataUsagePolicy build();
+ method public android.app.usage.DataUsagePolicy.Builder setNetworkType(int);
+ method public android.app.usage.DataUsagePolicy.Builder setThreshold(long);
+ }
+
public final class NetworkStats implements java.lang.AutoCloseable {
method public void close();
method public boolean getNextBucket(android.app.usage.NetworkStats.Bucket);
@@ -6360,6 +6379,14 @@
method public android.app.usage.NetworkStats.Bucket querySummaryForDevice(int, java.lang.String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
method public android.app.usage.NetworkStats.Bucket querySummaryForUser(int, java.lang.String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
method public android.app.usage.NetworkStats.Bucket querySummaryForUser(int, java.lang.String, long, long, boolean) throws android.os.RemoteException, java.lang.SecurityException;
+ method public void registerDataUsageCallback(android.app.usage.DataUsagePolicy, android.app.usage.NetworkStatsManager.DataUsageCallback);
+ method public void registerDataUsageCallback(android.app.usage.DataUsagePolicy, android.app.usage.NetworkStatsManager.DataUsageCallback, android.os.Handler);
+ method public void unregisterDataUsageCallback(android.app.usage.NetworkStatsManager.DataUsageCallback);
+ }
+
+ public static class NetworkStatsManager.DataUsageCallback {
+ ctor public NetworkStatsManager.DataUsageCallback();
+ method public void onLimitReached();
}
public final class UsageEvents implements android.os.Parcelable {
@@ -8061,6 +8088,7 @@
field public static final int BIND_ALLOW_OOM_MANAGEMENT = 16; // 0x10
field public static final int BIND_AUTO_CREATE = 1; // 0x1
field public static final int BIND_DEBUG_UNBIND = 2; // 0x2
+ field public static final int BIND_EXTERNAL_SERVICE = -2147483648; // 0x80000000
field public static final int BIND_IMPORTANT = 64; // 0x40
field public static final int BIND_NOT_FOREGROUND = 4; // 0x4
field public static final int BIND_WAIVE_PRIORITY = 32; // 0x20
@@ -9916,6 +9944,7 @@
method public int describeContents();
method public void dump(android.util.Printer, java.lang.String);
field public static final android.os.Parcelable.Creator<android.content.pm.ServiceInfo> CREATOR;
+ field public static final int FLAG_EXTERNAL_SERVICE = 4; // 0x4
field public static final int FLAG_ISOLATED_PROCESS = 2; // 0x2
field public static final int FLAG_SINGLE_USER = 1073741824; // 0x40000000
field public static final int FLAG_STOP_WITH_TASK = 1; // 0x1
@@ -23445,6 +23474,12 @@
method public int getUid();
}
+ public class DataUsageRequest implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.net.DataUsageRequest> CREATOR;
+ }
+
public class DhcpInfo implements android.os.Parcelable {
ctor public DhcpInfo();
method public int describeContents();
@@ -24311,22 +24346,6 @@
package android.net.wifi {
- public class ScanInfo implements android.os.Parcelable {
- ctor public ScanInfo(android.net.wifi.ScanResult);
- ctor public ScanInfo(long, int, java.lang.String, java.lang.String, java.lang.String, java.lang.String, byte[], int);
- method public int describeContents();
- method public long getBssid();
- method public byte[] getIconData();
- method public java.lang.String getIconType();
- method public java.lang.String getName();
- method public int getOsuIdentity();
- method public int getRssi();
- method public android.net.wifi.ScanResult getScanResult();
- method public java.lang.String getServiceDescription();
- method public java.lang.String getSsid();
- method public void writeToParcel(android.os.Parcel, int);
- }
-
public class ScanResult implements android.os.Parcelable {
method public int describeContents();
method public boolean is80211mcResponder();
@@ -24487,6 +24506,7 @@
field public static final int SIM = 4; // 0x4
field public static final int TLS = 1; // 0x1
field public static final int TTLS = 2; // 0x2
+ field public static final int UNAUTH_TLS = 7; // 0x7
}
public static final class WifiEnterpriseConfig.Phase2 {
@@ -24529,7 +24549,6 @@
method public java.util.List<android.net.wifi.WifiConfiguration> getConfiguredNetworks();
method public android.net.wifi.WifiInfo getConnectionInfo();
method public android.net.DhcpInfo getDhcpInfo();
- method public java.util.List<android.net.wifi.ScanInfo> getScanInfos();
method public java.util.List<android.net.wifi.ScanResult> getScanResults();
method public int getWifiState();
method public boolean is5GHzBandSupported();
@@ -24545,7 +24564,6 @@
method public boolean reconnect();
method public boolean removeNetwork(int);
method public boolean saveConfiguration();
- method public void setOsuSelection(int);
method public void setTdlsEnabled(java.net.InetAddress, boolean);
method public void setTdlsEnabledWithMacAddress(java.lang.String, boolean);
method public boolean setWifiEnabled(boolean);
@@ -32094,6 +32112,7 @@
field public static final java.lang.String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS";
field public static final java.lang.String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
field public static final java.lang.String ACTION_HOME_SETTINGS = "android.settings.HOME_SETTINGS";
+ field public static final java.lang.String ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS = "android.settings.IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS";
field public static final java.lang.String ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS = "android.settings.IGNORE_BATTERY_OPTIMIZATION_SETTINGS";
field public static final java.lang.String ACTION_INPUT_METHOD_SETTINGS = "android.settings.INPUT_METHOD_SETTINGS";
field public static final java.lang.String ACTION_INPUT_METHOD_SUBTYPE_SETTINGS = "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS";
@@ -36335,6 +36354,7 @@
method public boolean handleMmi(java.lang.String, android.telecom.PhoneAccountHandle);
method public boolean isInCall();
method public boolean isVoiceMailNumber(android.telecom.PhoneAccountHandle, java.lang.String);
+ method public void launchManageBlockedNumbersActivity();
method public void placeCall(android.net.Uri, android.os.Bundle);
method public void registerPhoneAccount(android.telecom.PhoneAccount);
method public void showInCallScreen(boolean);
@@ -50188,13 +50208,16 @@
method public static java.lang.Class<?> forName(java.lang.String, boolean, java.lang.ClassLoader) throws java.lang.ClassNotFoundException;
method public A getAnnotation(java.lang.Class<A>);
method public java.lang.annotation.Annotation[] getAnnotations();
+ method public T[] getAnnotationsByType(java.lang.Class<T>);
method public java.lang.String getCanonicalName();
method public java.lang.ClassLoader getClassLoader();
method public java.lang.Class<?>[] getClasses();
method public java.lang.Class<?> getComponentType();
method public java.lang.reflect.Constructor<T> getConstructor(java.lang.Class<?>...) throws java.lang.NoSuchMethodException, java.lang.SecurityException;
method public java.lang.reflect.Constructor<?>[] getConstructors() throws java.lang.SecurityException;
+ method public T getDeclaredAnnotation(java.lang.Class<T>);
method public java.lang.annotation.Annotation[] getDeclaredAnnotations();
+ method public T[] getDeclaredAnnotationsByType(java.lang.Class<T>);
method public java.lang.Class<?>[] getDeclaredClasses();
method public java.lang.reflect.Constructor<T> getDeclaredConstructor(java.lang.Class<?>...) throws java.lang.NoSuchMethodException, java.lang.SecurityException;
method public java.lang.reflect.Constructor<?>[] getDeclaredConstructors() throws java.lang.SecurityException;
@@ -50725,7 +50748,10 @@
public class Package implements java.lang.reflect.AnnotatedElement {
method public A getAnnotation(java.lang.Class<A>);
method public java.lang.annotation.Annotation[] getAnnotations();
+ method public T[] getAnnotationsByType(java.lang.Class<T>);
+ method public java.lang.annotation.Annotation getDeclaredAnnotation(java.lang.Class<T>);
method public java.lang.annotation.Annotation[] getDeclaredAnnotations();
+ method public T[] getDeclaredAnnotationsByType(java.lang.Class<T>);
method public java.lang.String getImplementationTitle();
method public java.lang.String getImplementationVendor();
method public java.lang.String getImplementationVersion();
@@ -51419,7 +51445,10 @@
ctor protected AccessibleObject();
method public T getAnnotation(java.lang.Class<T>);
method public java.lang.annotation.Annotation[] getAnnotations();
+ method public T[] getAnnotationsByType(java.lang.Class<T>);
+ method public java.lang.annotation.Annotation getDeclaredAnnotation(java.lang.Class<T>);
method public java.lang.annotation.Annotation[] getDeclaredAnnotations();
+ method public T[] getDeclaredAnnotationsByType(java.lang.Class<T>);
method public boolean isAccessible();
method public boolean isAnnotationPresent(java.lang.Class<? extends java.lang.annotation.Annotation>);
method public static void setAccessible(java.lang.reflect.AccessibleObject[], boolean) throws java.lang.SecurityException;
@@ -51429,7 +51458,10 @@
public abstract interface AnnotatedElement {
method public abstract T getAnnotation(java.lang.Class<T>);
method public abstract java.lang.annotation.Annotation[] getAnnotations();
+ method public abstract T[] getAnnotationsByType(java.lang.Class<T>);
+ method public abstract java.lang.annotation.Annotation getDeclaredAnnotation(java.lang.Class<T>);
method public abstract java.lang.annotation.Annotation[] getDeclaredAnnotations();
+ method public abstract T[] getDeclaredAnnotationsByType(java.lang.Class<T>);
method public abstract boolean isAnnotationPresent(java.lang.Class<? extends java.lang.annotation.Annotation>);
}
diff --git a/api/system-current.txt b/api/system-current.txt
index 10d1b04..a43e3f6 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -91,6 +91,7 @@
field public static final java.lang.String DEVICE_POWER = "android.permission.DEVICE_POWER";
field public static final java.lang.String DIAGNOSTIC = "android.permission.DIAGNOSTIC";
field public static final java.lang.String DISABLE_KEYGUARD = "android.permission.DISABLE_KEYGUARD";
+ field public static final java.lang.String DISPATCH_PROVISIONING_MESSAGE = "android.permission.DISPATCH_PROVISIONING_MESSAGE";
field public static final java.lang.String DUMP = "android.permission.DUMP";
field public static final java.lang.String EXPAND_STATUS_BAR = "android.permission.EXPAND_STATUS_BAR";
field public static final java.lang.String FACTORY_TEST = "android.permission.FACTORY_TEST";
@@ -401,6 +402,7 @@
field public static final int baselineAlignBottom = 16843042; // 0x1010122
field public static final int baselineAligned = 16843046; // 0x1010126
field public static final int baselineAlignedChildIndex = 16843047; // 0x1010127
+ field public static final int bitmap = 16844055; // 0x1010517
field public static final int borderlessButtonStyle = 16843563; // 0x101032b
field public static final int bottom = 16843184; // 0x10101b0
field public static final int bottomBright = 16842957; // 0x10100cd
@@ -742,6 +744,8 @@
field public static final int horizontalScrollViewStyle = 16843603; // 0x1010353
field public static final int horizontalSpacing = 16843028; // 0x1010114
field public static final int host = 16842792; // 0x1010028
+ field public static final int hotSpotX = 16844056; // 0x1010518
+ field public static final int hotSpotY = 16844057; // 0x1010519
field public static final int hyphenationFrequency = 16843998; // 0x10104de
field public static final int icon = 16842754; // 0x1010002
field public static final int iconPreview = 16843337; // 0x1010249
@@ -6100,6 +6104,7 @@
field public static final java.lang.String ACTION_DEVICE_OWNER_CHANGED = "android.app.action.DEVICE_OWNER_CHANGED";
field public static final java.lang.String ACTION_MANAGED_PROFILE_PROVISIONED = "android.app.action.MANAGED_PROFILE_PROVISIONED";
field public static final java.lang.String ACTION_PROVISION_MANAGED_DEVICE = "android.app.action.PROVISION_MANAGED_DEVICE";
+ field public static final java.lang.String ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
field public static final java.lang.String ACTION_PROVISION_MANAGED_PROFILE = "android.app.action.PROVISION_MANAGED_PROFILE";
field public static final java.lang.String ACTION_SET_NEW_PARENT_PROFILE_PASSWORD = "android.app.action.SET_NEW_PARENT_PROFILE_PASSWORD";
field public static final java.lang.String ACTION_SET_NEW_PASSWORD = "android.app.action.SET_NEW_PASSWORD";
@@ -6577,6 +6582,22 @@
field public static final android.os.Parcelable.Creator<android.app.usage.ConfigurationStats> CREATOR;
}
+ public class DataUsagePolicy {
+ field public final int networkType;
+ field public final java.lang.String[] subscriberIds;
+ field public final long thresholdInBytes;
+ field public final int[] uids;
+ }
+
+ public static class DataUsagePolicy.Builder {
+ ctor public DataUsagePolicy.Builder();
+ method public android.app.usage.DataUsagePolicy.Builder addSubscriberId(java.lang.String);
+ method public android.app.usage.DataUsagePolicy.Builder addUid(int);
+ method public android.app.usage.DataUsagePolicy build();
+ method public android.app.usage.DataUsagePolicy.Builder setNetworkType(int);
+ method public android.app.usage.DataUsagePolicy.Builder setThreshold(long);
+ }
+
public final class NetworkStats implements java.lang.AutoCloseable {
method public void close();
method public boolean getNextBucket(android.app.usage.NetworkStats.Bucket);
@@ -6617,6 +6638,14 @@
method public android.app.usage.NetworkStats.Bucket querySummaryForDevice(int, java.lang.String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
method public android.app.usage.NetworkStats.Bucket querySummaryForUser(int, java.lang.String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
method public android.app.usage.NetworkStats.Bucket querySummaryForUser(int, java.lang.String, long, long, boolean) throws android.os.RemoteException, java.lang.SecurityException;
+ method public void registerDataUsageCallback(android.app.usage.DataUsagePolicy, android.app.usage.NetworkStatsManager.DataUsageCallback);
+ method public void registerDataUsageCallback(android.app.usage.DataUsagePolicy, android.app.usage.NetworkStatsManager.DataUsageCallback, android.os.Handler);
+ method public void unregisterDataUsageCallback(android.app.usage.NetworkStatsManager.DataUsageCallback);
+ }
+
+ public static class NetworkStatsManager.DataUsageCallback {
+ ctor public NetworkStatsManager.DataUsageCallback();
+ method public void onLimitReached();
}
public final class UsageEvents implements android.os.Parcelable {
@@ -10317,6 +10346,7 @@
method public int describeContents();
method public void dump(android.util.Printer, java.lang.String);
field public static final android.os.Parcelable.Creator<android.content.pm.ServiceInfo> CREATOR;
+ field public static final int FLAG_EXTERNAL_SERVICE = 4; // 0x4
field public static final int FLAG_ISOLATED_PROCESS = 2; // 0x2
field public static final int FLAG_SINGLE_USER = 1073741824; // 0x40000000
field public static final int FLAG_STOP_WITH_TASK = 1; // 0x1
@@ -25250,6 +25280,12 @@
method public int getUid();
}
+ public class DataUsageRequest implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.net.DataUsageRequest> CREATOR;
+ }
+
public class DhcpInfo implements android.os.Parcelable {
ctor public DhcpInfo();
method public int describeContents();
@@ -26359,22 +26395,6 @@
field public byte id;
}
- public class ScanInfo implements android.os.Parcelable {
- ctor public ScanInfo(android.net.wifi.ScanResult);
- ctor public ScanInfo(long, int, java.lang.String, java.lang.String, java.lang.String, java.lang.String, byte[], int);
- method public int describeContents();
- method public long getBssid();
- method public byte[] getIconData();
- method public java.lang.String getIconType();
- method public java.lang.String getName();
- method public int getOsuIdentity();
- method public int getRssi();
- method public android.net.wifi.ScanResult getScanResult();
- method public java.lang.String getServiceDescription();
- method public java.lang.String getSsid();
- method public void writeToParcel(android.os.Parcel, int);
- }
-
public class ScanResult implements android.os.Parcelable {
method public int describeContents();
method public boolean is80211mcResponder();
@@ -26558,6 +26578,7 @@
field public static final int SIM = 4; // 0x4
field public static final int TLS = 1; // 0x1
field public static final int TTLS = 2; // 0x2
+ field public static final int UNAUTH_TLS = 7; // 0x7
}
public static final class WifiEnterpriseConfig.Phase2 {
@@ -26603,7 +26624,6 @@
method public android.net.wifi.WifiConnectionStatistics getConnectionStatistics();
method public android.net.DhcpInfo getDhcpInfo();
method public java.util.List<android.net.wifi.WifiConfiguration> getPrivilegedConfiguredNetworks();
- method public java.util.List<android.net.wifi.ScanInfo> getScanInfos();
method public java.util.List<android.net.wifi.ScanResult> getScanResults();
method public android.net.wifi.WifiConfiguration getWifiApConfiguration();
method public int getWifiApState();
@@ -26626,7 +26646,6 @@
method public boolean reconnect();
method public boolean removeNetwork(int);
method public boolean saveConfiguration();
- method public void setOsuSelection(int);
method public void setTdlsEnabled(java.net.InetAddress, boolean);
method public void setTdlsEnabledWithMacAddress(java.lang.String, boolean);
method public boolean setWifiApConfiguration(android.net.wifi.WifiConfiguration);
@@ -34502,6 +34521,7 @@
field public static final java.lang.String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS";
field public static final java.lang.String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
field public static final java.lang.String ACTION_HOME_SETTINGS = "android.settings.HOME_SETTINGS";
+ field public static final java.lang.String ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS = "android.settings.IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS";
field public static final java.lang.String ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS = "android.settings.IGNORE_BATTERY_OPTIMIZATION_SETTINGS";
field public static final java.lang.String ACTION_INPUT_METHOD_SETTINGS = "android.settings.INPUT_METHOD_SETTINGS";
field public static final java.lang.String ACTION_INPUT_METHOD_SUBTYPE_SETTINGS = "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS";
@@ -38911,6 +38931,7 @@
method public boolean isRinging();
method public boolean isTtySupported();
method public boolean isVoiceMailNumber(android.telecom.PhoneAccountHandle, java.lang.String);
+ method public void launchManageBlockedNumbersActivity();
method public void placeCall(android.net.Uri, android.os.Bundle);
method public void registerPhoneAccount(android.telecom.PhoneAccount);
method public void showInCallScreen(boolean);
@@ -53171,13 +53192,16 @@
method public static java.lang.Class<?> forName(java.lang.String, boolean, java.lang.ClassLoader) throws java.lang.ClassNotFoundException;
method public A getAnnotation(java.lang.Class<A>);
method public java.lang.annotation.Annotation[] getAnnotations();
+ method public T[] getAnnotationsByType(java.lang.Class<T>);
method public java.lang.String getCanonicalName();
method public java.lang.ClassLoader getClassLoader();
method public java.lang.Class<?>[] getClasses();
method public java.lang.Class<?> getComponentType();
method public java.lang.reflect.Constructor<T> getConstructor(java.lang.Class<?>...) throws java.lang.NoSuchMethodException, java.lang.SecurityException;
method public java.lang.reflect.Constructor<?>[] getConstructors() throws java.lang.SecurityException;
+ method public T getDeclaredAnnotation(java.lang.Class<T>);
method public java.lang.annotation.Annotation[] getDeclaredAnnotations();
+ method public T[] getDeclaredAnnotationsByType(java.lang.Class<T>);
method public java.lang.Class<?>[] getDeclaredClasses();
method public java.lang.reflect.Constructor<T> getDeclaredConstructor(java.lang.Class<?>...) throws java.lang.NoSuchMethodException, java.lang.SecurityException;
method public java.lang.reflect.Constructor<?>[] getDeclaredConstructors() throws java.lang.SecurityException;
@@ -53708,7 +53732,10 @@
public class Package implements java.lang.reflect.AnnotatedElement {
method public A getAnnotation(java.lang.Class<A>);
method public java.lang.annotation.Annotation[] getAnnotations();
+ method public T[] getAnnotationsByType(java.lang.Class<T>);
+ method public java.lang.annotation.Annotation getDeclaredAnnotation(java.lang.Class<T>);
method public java.lang.annotation.Annotation[] getDeclaredAnnotations();
+ method public T[] getDeclaredAnnotationsByType(java.lang.Class<T>);
method public java.lang.String getImplementationTitle();
method public java.lang.String getImplementationVendor();
method public java.lang.String getImplementationVersion();
@@ -54402,7 +54429,10 @@
ctor protected AccessibleObject();
method public T getAnnotation(java.lang.Class<T>);
method public java.lang.annotation.Annotation[] getAnnotations();
+ method public T[] getAnnotationsByType(java.lang.Class<T>);
+ method public java.lang.annotation.Annotation getDeclaredAnnotation(java.lang.Class<T>);
method public java.lang.annotation.Annotation[] getDeclaredAnnotations();
+ method public T[] getDeclaredAnnotationsByType(java.lang.Class<T>);
method public boolean isAccessible();
method public boolean isAnnotationPresent(java.lang.Class<? extends java.lang.annotation.Annotation>);
method public static void setAccessible(java.lang.reflect.AccessibleObject[], boolean) throws java.lang.SecurityException;
@@ -54412,7 +54442,10 @@
public abstract interface AnnotatedElement {
method public abstract T getAnnotation(java.lang.Class<T>);
method public abstract java.lang.annotation.Annotation[] getAnnotations();
+ method public abstract T[] getAnnotationsByType(java.lang.Class<T>);
+ method public abstract java.lang.annotation.Annotation getDeclaredAnnotation(java.lang.Class<T>);
method public abstract java.lang.annotation.Annotation[] getDeclaredAnnotations();
+ method public abstract T[] getDeclaredAnnotationsByType(java.lang.Class<T>);
method public abstract boolean isAnnotationPresent(java.lang.Class<? extends java.lang.annotation.Annotation>);
}
diff --git a/api/test-current.txt b/api/test-current.txt
index a667a50..278a47d 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -306,6 +306,7 @@
field public static final int baselineAlignBottom = 16843042; // 0x1010122
field public static final int baselineAligned = 16843046; // 0x1010126
field public static final int baselineAlignedChildIndex = 16843047; // 0x1010127
+ field public static final int bitmap = 16844055; // 0x1010517
field public static final int borderlessButtonStyle = 16843563; // 0x101032b
field public static final int bottom = 16843184; // 0x10101b0
field public static final int bottomBright = 16842957; // 0x10100cd
@@ -647,6 +648,8 @@
field public static final int horizontalScrollViewStyle = 16843603; // 0x1010353
field public static final int horizontalSpacing = 16843028; // 0x1010114
field public static final int host = 16842792; // 0x1010028
+ field public static final int hotSpotX = 16844056; // 0x1010518
+ field public static final int hotSpotY = 16844057; // 0x1010519
field public static final int hyphenationFrequency = 16843998; // 0x10104de
field public static final int icon = 16842754; // 0x1010002
field public static final int iconPreview = 16843337; // 0x1010249
@@ -6322,6 +6325,22 @@
field public static final android.os.Parcelable.Creator<android.app.usage.ConfigurationStats> CREATOR;
}
+ public class DataUsagePolicy {
+ field public final int networkType;
+ field public final java.lang.String[] subscriberIds;
+ field public final long thresholdInBytes;
+ field public final int[] uids;
+ }
+
+ public static class DataUsagePolicy.Builder {
+ ctor public DataUsagePolicy.Builder();
+ method public android.app.usage.DataUsagePolicy.Builder addSubscriberId(java.lang.String);
+ method public android.app.usage.DataUsagePolicy.Builder addUid(int);
+ method public android.app.usage.DataUsagePolicy build();
+ method public android.app.usage.DataUsagePolicy.Builder setNetworkType(int);
+ method public android.app.usage.DataUsagePolicy.Builder setThreshold(long);
+ }
+
public final class NetworkStats implements java.lang.AutoCloseable {
method public void close();
method public boolean getNextBucket(android.app.usage.NetworkStats.Bucket);
@@ -6362,6 +6381,14 @@
method public android.app.usage.NetworkStats.Bucket querySummaryForDevice(int, java.lang.String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
method public android.app.usage.NetworkStats.Bucket querySummaryForUser(int, java.lang.String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
method public android.app.usage.NetworkStats.Bucket querySummaryForUser(int, java.lang.String, long, long, boolean) throws android.os.RemoteException, java.lang.SecurityException;
+ method public void registerDataUsageCallback(android.app.usage.DataUsagePolicy, android.app.usage.NetworkStatsManager.DataUsageCallback);
+ method public void registerDataUsageCallback(android.app.usage.DataUsagePolicy, android.app.usage.NetworkStatsManager.DataUsageCallback, android.os.Handler);
+ method public void unregisterDataUsageCallback(android.app.usage.NetworkStatsManager.DataUsageCallback);
+ }
+
+ public static class NetworkStatsManager.DataUsageCallback {
+ ctor public NetworkStatsManager.DataUsageCallback();
+ method public void onLimitReached();
}
public final class UsageEvents implements android.os.Parcelable {
@@ -8065,6 +8092,7 @@
field public static final int BIND_ALLOW_OOM_MANAGEMENT = 16; // 0x10
field public static final int BIND_AUTO_CREATE = 1; // 0x1
field public static final int BIND_DEBUG_UNBIND = 2; // 0x2
+ field public static final int BIND_EXTERNAL_SERVICE = -2147483648; // 0x80000000
field public static final int BIND_IMPORTANT = 64; // 0x40
field public static final int BIND_NOT_FOREGROUND = 4; // 0x4
field public static final int BIND_WAIVE_PRIORITY = 32; // 0x20
@@ -9924,6 +9952,7 @@
method public int describeContents();
method public void dump(android.util.Printer, java.lang.String);
field public static final android.os.Parcelable.Creator<android.content.pm.ServiceInfo> CREATOR;
+ field public static final int FLAG_EXTERNAL_SERVICE = 4; // 0x4
field public static final int FLAG_ISOLATED_PROCESS = 2; // 0x2
field public static final int FLAG_SINGLE_USER = 1073741824; // 0x40000000
field public static final int FLAG_STOP_WITH_TASK = 1; // 0x1
@@ -23454,6 +23483,12 @@
method public int getUid();
}
+ public class DataUsageRequest implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.net.DataUsageRequest> CREATOR;
+ }
+
public class DhcpInfo implements android.os.Parcelable {
ctor public DhcpInfo();
method public int describeContents();
@@ -24320,22 +24355,6 @@
package android.net.wifi {
- public class ScanInfo implements android.os.Parcelable {
- ctor public ScanInfo(android.net.wifi.ScanResult);
- ctor public ScanInfo(long, int, java.lang.String, java.lang.String, java.lang.String, java.lang.String, byte[], int);
- method public int describeContents();
- method public long getBssid();
- method public byte[] getIconData();
- method public java.lang.String getIconType();
- method public java.lang.String getName();
- method public int getOsuIdentity();
- method public int getRssi();
- method public android.net.wifi.ScanResult getScanResult();
- method public java.lang.String getServiceDescription();
- method public java.lang.String getSsid();
- method public void writeToParcel(android.os.Parcel, int);
- }
-
public class ScanResult implements android.os.Parcelable {
method public int describeContents();
method public boolean is80211mcResponder();
@@ -24496,6 +24515,7 @@
field public static final int SIM = 4; // 0x4
field public static final int TLS = 1; // 0x1
field public static final int TTLS = 2; // 0x2
+ field public static final int UNAUTH_TLS = 7; // 0x7
}
public static final class WifiEnterpriseConfig.Phase2 {
@@ -24538,7 +24558,6 @@
method public java.util.List<android.net.wifi.WifiConfiguration> getConfiguredNetworks();
method public android.net.wifi.WifiInfo getConnectionInfo();
method public android.net.DhcpInfo getDhcpInfo();
- method public java.util.List<android.net.wifi.ScanInfo> getScanInfos();
method public java.util.List<android.net.wifi.ScanResult> getScanResults();
method public int getWifiState();
method public boolean is5GHzBandSupported();
@@ -24554,7 +24573,6 @@
method public boolean reconnect();
method public boolean removeNetwork(int);
method public boolean saveConfiguration();
- method public void setOsuSelection(int);
method public void setTdlsEnabled(java.net.InetAddress, boolean);
method public void setTdlsEnabledWithMacAddress(java.lang.String, boolean);
method public boolean setWifiEnabled(boolean);
@@ -32107,6 +32125,7 @@
field public static final java.lang.String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS";
field public static final java.lang.String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
field public static final java.lang.String ACTION_HOME_SETTINGS = "android.settings.HOME_SETTINGS";
+ field public static final java.lang.String ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS = "android.settings.IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS";
field public static final java.lang.String ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS = "android.settings.IGNORE_BATTERY_OPTIMIZATION_SETTINGS";
field public static final java.lang.String ACTION_INPUT_METHOD_SETTINGS = "android.settings.INPUT_METHOD_SETTINGS";
field public static final java.lang.String ACTION_INPUT_METHOD_SUBTYPE_SETTINGS = "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS";
@@ -36350,6 +36369,7 @@
method public boolean handleMmi(java.lang.String, android.telecom.PhoneAccountHandle);
method public boolean isInCall();
method public boolean isVoiceMailNumber(android.telecom.PhoneAccountHandle, java.lang.String);
+ method public void launchManageBlockedNumbersActivity();
method public void placeCall(android.net.Uri, android.os.Bundle);
method public void registerPhoneAccount(android.telecom.PhoneAccount);
method public void showInCallScreen(boolean);
@@ -50205,13 +50225,16 @@
method public static java.lang.Class<?> forName(java.lang.String, boolean, java.lang.ClassLoader) throws java.lang.ClassNotFoundException;
method public A getAnnotation(java.lang.Class<A>);
method public java.lang.annotation.Annotation[] getAnnotations();
+ method public T[] getAnnotationsByType(java.lang.Class<T>);
method public java.lang.String getCanonicalName();
method public java.lang.ClassLoader getClassLoader();
method public java.lang.Class<?>[] getClasses();
method public java.lang.Class<?> getComponentType();
method public java.lang.reflect.Constructor<T> getConstructor(java.lang.Class<?>...) throws java.lang.NoSuchMethodException, java.lang.SecurityException;
method public java.lang.reflect.Constructor<?>[] getConstructors() throws java.lang.SecurityException;
+ method public T getDeclaredAnnotation(java.lang.Class<T>);
method public java.lang.annotation.Annotation[] getDeclaredAnnotations();
+ method public T[] getDeclaredAnnotationsByType(java.lang.Class<T>);
method public java.lang.Class<?>[] getDeclaredClasses();
method public java.lang.reflect.Constructor<T> getDeclaredConstructor(java.lang.Class<?>...) throws java.lang.NoSuchMethodException, java.lang.SecurityException;
method public java.lang.reflect.Constructor<?>[] getDeclaredConstructors() throws java.lang.SecurityException;
@@ -50742,7 +50765,10 @@
public class Package implements java.lang.reflect.AnnotatedElement {
method public A getAnnotation(java.lang.Class<A>);
method public java.lang.annotation.Annotation[] getAnnotations();
+ method public T[] getAnnotationsByType(java.lang.Class<T>);
+ method public java.lang.annotation.Annotation getDeclaredAnnotation(java.lang.Class<T>);
method public java.lang.annotation.Annotation[] getDeclaredAnnotations();
+ method public T[] getDeclaredAnnotationsByType(java.lang.Class<T>);
method public java.lang.String getImplementationTitle();
method public java.lang.String getImplementationVendor();
method public java.lang.String getImplementationVersion();
@@ -51436,7 +51462,10 @@
ctor protected AccessibleObject();
method public T getAnnotation(java.lang.Class<T>);
method public java.lang.annotation.Annotation[] getAnnotations();
+ method public T[] getAnnotationsByType(java.lang.Class<T>);
+ method public java.lang.annotation.Annotation getDeclaredAnnotation(java.lang.Class<T>);
method public java.lang.annotation.Annotation[] getDeclaredAnnotations();
+ method public T[] getDeclaredAnnotationsByType(java.lang.Class<T>);
method public boolean isAccessible();
method public boolean isAnnotationPresent(java.lang.Class<? extends java.lang.annotation.Annotation>);
method public static void setAccessible(java.lang.reflect.AccessibleObject[], boolean) throws java.lang.SecurityException;
@@ -51446,7 +51475,10 @@
public abstract interface AnnotatedElement {
method public abstract T getAnnotation(java.lang.Class<T>);
method public abstract java.lang.annotation.Annotation[] getAnnotations();
+ method public abstract T[] getAnnotationsByType(java.lang.Class<T>);
+ method public abstract java.lang.annotation.Annotation getDeclaredAnnotation(java.lang.Class<T>);
method public abstract java.lang.annotation.Annotation[] getDeclaredAnnotations();
+ method public abstract T[] getDeclaredAnnotationsByType(java.lang.Class<T>);
method public abstract boolean isAnnotationPresent(java.lang.Class<? extends java.lang.annotation.Annotation>);
}
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 9d81c43..a74a1ca 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -1774,18 +1774,33 @@
System.err.println("Error: invalid input bounds");
return;
}
- resizeStack(stackId, bounds, 0, false);
+ resizeStack(stackId, bounds, 0);
}
private void runStackResizeAnimated() throws Exception {
String stackIdStr = nextArgRequired();
int stackId = Integer.valueOf(stackIdStr);
- final Rect bounds = getBounds();
- if (bounds == null) {
- System.err.println("Error: invalid input bounds");
- return;
+ final Rect bounds;
+ if ("null".equals(mArgs.peekNextArg())) {
+ bounds = null;
+ } else {
+ bounds = getBounds();
+ if (bounds == null) {
+ System.err.println("Error: invalid input bounds");
+ return;
+ }
}
- resizeStack(stackId, bounds, 0, true);
+ resizeStackUnchecked(stackId, bounds, 0, true);
+ }
+
+ private void resizeStackUnchecked(int stackId, Rect bounds, int delayMs, boolean animate) {
+ try {
+ mAm.resizeStack(stackId, bounds, false, false, animate);
+ Thread.sleep(delayMs);
+ } catch (RemoteException e) {
+ showError("Error: resizing stack " + e);
+ } catch (InterruptedException e) {
+ }
}
private void runStackResizeDocked() throws Exception {
@@ -1802,20 +1817,13 @@
}
}
- private void resizeStack(int stackId, Rect bounds, int delayMs, boolean animate)
+ private void resizeStack(int stackId, Rect bounds, int delayMs)
throws Exception {
if (bounds == null) {
showError("Error: invalid input bounds");
return;
}
-
- try {
- mAm.resizeStack(stackId, bounds, false, false, animate);
- Thread.sleep(delayMs);
- } catch (RemoteException e) {
- showError("Error: resizing stack " + e);
- } catch (InterruptedException e) {
- }
+ resizeStackUnchecked(stackId, bounds, delayMs, false);
}
private void runStackPositionTask() throws Exception {
@@ -1924,7 +1932,7 @@
maxChange = Math.min(stepSize, currentPoint - minPoint);
currentPoint -= maxChange;
setBoundsSide(bounds, side, currentPoint);
- resizeStack(DOCKED_STACK_ID, bounds, delayMs, false);
+ resizeStack(DOCKED_STACK_ID, bounds, delayMs);
}
System.out.println("Growing docked stack side=" + side);
@@ -1932,7 +1940,7 @@
maxChange = Math.min(stepSize, maxPoint - currentPoint);
currentPoint += maxChange;
setBoundsSide(bounds, side, currentPoint);
- resizeStack(DOCKED_STACK_ID, bounds, delayMs, false);
+ resizeStack(DOCKED_STACK_ID, bounds, delayMs);
}
System.out.println("Back to Original size side=" + side);
@@ -1940,7 +1948,7 @@
maxChange = Math.min(stepSize, currentPoint - startPoint);
currentPoint -= maxChange;
setBoundsSide(bounds, side, currentPoint);
- resizeStack(DOCKED_STACK_ID, bounds, delayMs, false);
+ resizeStack(DOCKED_STACK_ID, bounds, delayMs);
}
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 9bad2f9..02eb115 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -241,6 +241,48 @@
/**
* Activity action: Starts the provisioning flow which sets up a managed device.
+ *
+ * <p>During device owner provisioning, a device admin app is downloaded and 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 and the only way of resetting the device is via factory reset.
+ *
+ * <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 provisioning message should be sent to an unprovisioned device.
+ *
+ * <p>Unlike {@link #ACTION_PROVISION_MANAGED_DEVICE}, the provisioning message can only be sent
+ * by a privileged app with the permission
+ * {@link android.Manifest.permission#DISPATCH_PROVISIONING_MESSAGE}.
+ *
+ * <p>The provisioning intent contains the following properties:
+ * <ul>
+ * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}</li>
+ * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_LOCAL_TIME} (convert to String), optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_TIME_ZONE}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_LOCALE}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_WIFI_SSID}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_WIFI_HIDDEN} (convert to String), optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_WIFI_SECURITY_TYPE}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_WIFI_PASSWORD}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_WIFI_PROXY_HOST}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_WIFI_PROXY_PORT} (convert to String), optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_WIFI_PROXY_BYPASS}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_WIFI_PAC_URL}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li></ul>
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ @SystemApi
+ public static final String ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE =
+ "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
+
+ /**
+ * Activity action: Starts the provisioning flow which sets up a managed device.
* Must be started with {@link android.app.Activity#startActivityForResult(Intent, int)}.
*
* <p>NOTE: This is only supported on split system user devices, and puts the device into a
@@ -4067,6 +4109,29 @@
}
/**
+ * Called by the system to check if a specific accessibility service is disabled by admin.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param packageName Accessibility service package name that needs to be checked.
+ * @param userHandle user id the admin is running as.
+ * @return true if the accessibility service is permitted, otherwise false.
+ *
+ * @hide
+ */
+ public boolean isAccessibilityServicePermittedByAdmin(@NonNull ComponentName admin,
+ @NonNull String packageName, int userHandle) {
+ if (mService != null) {
+ try {
+ return mService.isAccessibilityServicePermittedByAdmin(admin, packageName,
+ userHandle);
+ } catch (RemoteException e) {
+ Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e);
+ }
+ }
+ return false;
+ }
+
+ /**
* Returns the list of accessibility services permitted by the device or profiles
* owners of this user.
*
@@ -4146,6 +4211,28 @@
}
/**
+ * Called by the system to check if a specific input method is disabled by admin.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param packageName Input method package name that needs to be checked.
+ * @param userHandle user id the admin is running as.
+ * @return true if the input method is permitted, otherwise false.
+ *
+ * @hide
+ */
+ public boolean isInputMethodPermittedByAdmin(@NonNull ComponentName admin,
+ @NonNull String packageName, int userHandle) {
+ if (mService != null) {
+ try {
+ return mService.isInputMethodPermittedByAdmin(admin, packageName, userHandle);
+ } catch (RemoteException e) {
+ Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e);
+ }
+ }
+ return false;
+ }
+
+ /**
* Returns the list of input methods permitted by the device or profiles
* owners of the current user. (*Not* calling user, due to a limitation in InputMethodManager.)
*
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index b57e1b7..c6a5344 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -174,10 +174,12 @@
boolean setPermittedAccessibilityServices(in ComponentName admin,in List packageList);
List getPermittedAccessibilityServices(in ComponentName admin);
List getPermittedAccessibilityServicesForUser(int userId);
+ boolean isAccessibilityServicePermittedByAdmin(in ComponentName admin, String packageName, int userId);
boolean setPermittedInputMethods(in ComponentName admin,in List packageList);
List getPermittedInputMethods(in ComponentName admin);
List getPermittedInputMethodsForCurrentUser();
+ boolean isInputMethodPermittedByAdmin(in ComponentName admin, String packageName, int userId);
boolean setApplicationHidden(in ComponentName admin, in String packageName, boolean hidden);
boolean isApplicationHidden(in ComponentName admin, in String packageName);
diff --git a/core/java/android/app/usage/DataUsagePolicy.java b/core/java/android/app/usage/DataUsagePolicy.java
new file mode 100644
index 0000000..5a5dcbc
--- /dev/null
+++ b/core/java/android/app/usage/DataUsagePolicy.java
@@ -0,0 +1,174 @@
+/**
+ * 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.app.usage;
+
+import android.net.ConnectivityManager;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.IntArray;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Defines a policy for data usage callbacks, made through {@link DataUsagePolicy.Builder} and used
+ * to be notified on data usage via {@link NetworkStatsManager#registerDataUsageCallback}.
+ */
+public class DataUsagePolicy {
+
+ /**
+ * Network type to be monitored, as defined in {@link ConnectivityManager}, e.g.
+ * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} etc.
+ */
+ public final int networkType;
+
+ /**
+ * Set of subscriber ids to be monitored for the given network type. May be empty if not
+ * applicable.
+ * <p>Should not be modified once created.
+ */
+ public final String[] subscriberIds;
+
+ /**
+ * Set of UIDs of which to monitor data usage.
+ *
+ * <p>If not {@code null}, the caller will be notified when any of the uids exceed
+ * the given threshold. If empty all uids for which the calling process has access
+ * to stats will be monitored.
+ * <p>Should not be modified once created.
+ */
+ public final int[] uids;
+
+ /**
+ * Threshold in bytes to be notified on.
+ */
+ public final long thresholdInBytes;
+
+ /**
+ * @hide
+ */
+ DataUsagePolicy(int networkType, String[] subscriberIds, int[] uids,
+ long thresholdInBytes) {
+ this.networkType = networkType;
+ this.subscriberIds = subscriberIds;
+ this.uids = uids;
+ this.thresholdInBytes = thresholdInBytes;
+ }
+
+ /**
+ * Builder used to create {@link DataUsagePolicy} objects.
+ */
+ public static class Builder {
+ private static final int INVALID_NETWORK_TYPE = -1;
+ private int mNetworkType = INVALID_NETWORK_TYPE;
+ private List<String> mSubscriberList = new ArrayList<>();
+ private IntArray mUids = new IntArray();
+ private long mThresholdInBytes;
+
+ /**
+ * Default constructor for Builder.
+ */
+ public Builder() {}
+
+ /**
+ * Build {@link DataUsagePolicy} given the current policies.
+ */
+ public DataUsagePolicy build() {
+ if (mNetworkType == INVALID_NETWORK_TYPE) {
+ throw new IllegalArgumentException(
+ "DataUsagePolicy requires a valid network type to be set");
+ }
+ return new DataUsagePolicy(mNetworkType,
+ mSubscriberList.toArray(new String[mSubscriberList.size()]),
+ mUids.toArray(), mThresholdInBytes);
+ }
+
+ /**
+ * Specifies that the given {@code subscriberId} should be monitored.
+ *
+ * @param subscriberId the subscriber id of the network interface.
+ */
+ public Builder addSubscriberId(String subscriberId) {
+ mSubscriberList.add(subscriberId);
+ return this;
+ }
+
+ /**
+ * Specifies that the given {@code uid} should be monitored.
+ */
+ public Builder addUid(int uid) {
+ mUids.add(uid);
+ return this;
+ }
+
+ /**
+ * Specifies that the callback should monitor the given network. It is mandatory
+ * to set one.
+ *
+ * @param networkType As defined in {@link ConnectivityManager}, e.g.
+ * {@link ConnectivityManager#TYPE_MOBILE},
+ * {@link ConnectivityManager#TYPE_WIFI}, etc.
+ */
+ public Builder setNetworkType(int networkType) {
+ mNetworkType = networkType;
+ return this;
+ }
+
+ /**
+ * Sets the threshold in bytes on which the listener should be called. The framework may
+ * impose a minimum threshold to avoid too many notifications to be triggered.
+ */
+ public Builder setThreshold(long thresholdInBytes) {
+ mThresholdInBytes = thresholdInBytes;
+ return this;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "DataUsagePolicy [ networkType=" + networkType
+ + ", subscriberIds=" + Arrays.toString(subscriberIds)
+ + ", uids=" + Arrays.toString(uids)
+ + ", thresholdInBytes=" + thresholdInBytes + " ]";
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof DataUsagePolicy == false) return false;
+ DataUsagePolicy that = (DataUsagePolicy) obj;
+ return that.networkType == this.networkType
+ && Arrays.deepEquals(that.subscriberIds, this.subscriberIds)
+ && Arrays.equals(that.uids, this.uids)
+ && that.thresholdInBytes == this.thresholdInBytes;
+ }
+
+ @Override
+ public int hashCode() {
+ // Start with a non-zero constant.
+ int result = 17;
+
+ // Include a hash for each field.
+ result = 31 * result + networkType;
+ result = 31 * result + Arrays.deepHashCode(subscriberIds);
+ result = 31 * result + Arrays.hashCode(uids);
+ result = 31 * result + (int) (thresholdInBytes ^ (thresholdInBytes >>> 32));
+
+ return result;
+ }
+}
diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java
index 4dc636b..13aeef0 100644
--- a/core/java/android/app/usage/NetworkStatsManager.java
+++ b/core/java/android/app/usage/NetworkStatsManager.java
@@ -16,12 +16,17 @@
package android.app.usage;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.Nullable;
import android.app.usage.NetworkStats.Bucket;
import android.content.Context;
import android.net.ConnectivityManager;
+import android.net.DataUsageRequest;
import android.net.NetworkIdentity;
import android.net.NetworkTemplate;
import android.os.Build;
+import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
@@ -288,6 +293,62 @@
return result;
}
+ /**
+ * Registers to receive notifications about data usage on specified networks and uids.
+ * The callbacks will continue to be called as long as the process is live or
+ * {@link #unregisterDataUsageCallback} is called.
+ *
+ * @param policy {@link DataUsagePolicy} describing this request.
+ * @param callback The {@link DataUsageCallback} that the system will call when data usage
+ * has exceeded the specified threshold.
+ */
+ public void registerDataUsageCallback(DataUsagePolicy policy, DataUsageCallback callback) {
+ registerDataUsageCallback(policy, callback, null /* handler */);
+ }
+
+ /**
+ * Registers to receive notifications about data usage on specified networks and uids.
+ * The callbacks will continue to be called as long as the process is live or
+ * {@link #unregisterDataUsageCallback} is called.
+ *
+ * @param policy {@link DataUsagePolicy} describing this request.
+ * @param callback The {@link DataUsageCallback} that the system will call when data usage
+ * has exceeded the specified threshold.
+ * @param handler to dispatch callback events through, otherwise if {@code null} it uses
+ * the calling thread.
+ */
+ public void registerDataUsageCallback(DataUsagePolicy policy, DataUsageCallback callback,
+ @Nullable Handler handler) {
+ checkNotNull(policy, "DataUsagePolicy cannot be null");
+ checkNotNull(callback, "DataUsageCallback cannot be null");
+
+ // TODO: Implement stub.
+ }
+
+ /**
+ * Unregisters callbacks on data usage.
+ *
+ * @param callback The {@link DataUsageCallback} used when registering.
+ */
+ public void unregisterDataUsageCallback(DataUsageCallback callback) {
+ checkNotNull(callback, "DataUsageCallback cannot be null");
+
+ // TODO: Implement stub.
+ }
+
+ /**
+ * Base class for data usage callbacks. Should be extended by applications wanting
+ * notifications.
+ */
+ public static class DataUsageCallback {
+ /**
+ * Called when data usage has reached the given policy threshold.
+ */
+ public void onLimitReached() {}
+
+ private DataUsageRequest request;
+ }
+
private static NetworkTemplate createTemplate(int networkType, String subscriberId) {
NetworkTemplate template = null;
switch (networkType) {
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index 55b8f03..bed91ec 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -29,6 +29,7 @@
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
+import android.os.CancellationSignal;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
@@ -49,6 +50,8 @@
import android.widget.RemoteViewsAdapter.RemoteAdapterConnectionCallback;
import android.widget.TextView;
+import java.util.concurrent.Executor;
+
/**
* Provides the glue to show AppWidget views. This class offers automatic animation
* between updates, and will try recycling old views for each incoming
@@ -87,6 +90,9 @@
Paint mOldPaint = new Paint();
private OnClickHandler mOnClickHandler;
+ private Executor mAsyncExecutor;
+ private CancellationSignal mLastExecutionSignal;
+
/**
* Create a host view. Uses default fade animations.
*/
@@ -340,6 +346,22 @@
}
/**
+ * Sets an executor which can be used for asynchronously inflating and applying the remoteviews.
+ * @see {@link RemoteViews#applyAsync(Context, ViewGroup, RemoteViews.OnViewAppliedListener, Executor)}
+ *
+ * @param executor the executor to use or null.
+ * @hide
+ */
+ public void setAsyncExecutor(Executor executor) {
+ if (mLastExecutionSignal != null) {
+ mLastExecutionSignal.cancel();
+ mLastExecutionSignal = null;
+ }
+
+ mAsyncExecutor = executor;
+ }
+
+ /**
* Update the AppWidgetProviderInfo for this view, and reset it to the
* initial layout.
*/
@@ -380,6 +402,11 @@
}
}
+ if (mLastExecutionSignal != null) {
+ mLastExecutionSignal.cancel();
+ mLastExecutionSignal = null;
+ }
+
if (remoteViews == null) {
if (mViewMode == VIEW_MODE_DEFAULT) {
// We've already done this -- nothing to do.
@@ -389,6 +416,10 @@
mLayoutId = -1;
mViewMode = VIEW_MODE_DEFAULT;
} else {
+ if (mAsyncExecutor != null) {
+ inflateAsync(remoteViews);
+ return;
+ }
// Prepare a local reference to the remote Context so we're ready to
// inflate any requested LayoutParams.
mRemoteContext = getRemoteContext();
@@ -421,6 +452,10 @@
mViewMode = VIEW_MODE_CONTENT;
}
+ applyContent(content, recycled, exception);
+ }
+
+ private void applyContent(View content, boolean recycled, Exception exception) {
if (content == null) {
if (mViewMode == VIEW_MODE_ERROR) {
// We've already done this -- nothing to do.
@@ -452,6 +487,68 @@
}
}
+ private void inflateAsync(RemoteViews remoteViews) {
+ // Prepare a local reference to the remote Context so we're ready to
+ // inflate any requested LayoutParams.
+ mRemoteContext = getRemoteContext();
+ int layoutId = remoteViews.getLayoutId();
+
+ // If our stale view has been prepared to match active, and the new
+ // layout matches, try recycling it
+ if (layoutId == mLayoutId && mView != null) {
+ try {
+ mLastExecutionSignal = remoteViews.reapplyAsync(mContext,
+ mView,
+ mAsyncExecutor,
+ new ViewApplyListener(remoteViews, layoutId, true),
+ mOnClickHandler);
+ } catch (Exception e) {
+ // Reapply failed. Try apply
+ }
+ }
+ if (mLastExecutionSignal == null) {
+ mLastExecutionSignal = remoteViews.applyAsync(mContext,
+ this,
+ mAsyncExecutor,
+ new ViewApplyListener(remoteViews, layoutId, false),
+ mOnClickHandler);
+ }
+ }
+
+ private class ViewApplyListener implements RemoteViews.OnViewAppliedListener {
+ private final RemoteViews mViews;
+ private final boolean mIsReapply;
+ private final int mLayoutId;
+
+ public ViewApplyListener(RemoteViews views, int layoutId, boolean isReapply) {
+ mViews = views;
+ mLayoutId = layoutId;
+ mIsReapply = isReapply;
+ }
+
+ @Override
+ public void onViewApplied(View v) {
+ AppWidgetHostView.this.mLayoutId = mLayoutId;
+ mViewMode = VIEW_MODE_CONTENT;
+
+ applyContent(v, mIsReapply, null);
+ }
+
+ @Override
+ public void onError(Exception e) {
+ if (mIsReapply) {
+ // Try a fresh replay
+ mLastExecutionSignal = mViews.applyAsync(mContext,
+ AppWidgetHostView.this,
+ mAsyncExecutor,
+ new ViewApplyListener(mViews, mLayoutId, false),
+ mOnClickHandler);
+ } else {
+ applyContent(null, false, e);
+ }
+ }
+ }
+
/**
* Process data-changed notifications for the specified view in the specified
* set of {@link RemoteViews} views.
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 622aad9..4918914 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -342,9 +342,7 @@
* {@link android.R.attr#isolatedProcess isolated},
* {@link android.R.attr#externalService external} service. This binds the service into the
* calling application's package, rather than the package in which the service is declared.
- * @hide
*/
- @SystemApi
public static final int BIND_EXTERNAL_SERVICE = 0x80000000;
/**
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index eecf0de..6bd285a 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -52,7 +52,6 @@
* Bit in {@link #flags}: If set, the service can be bound and run in the
* calling application's package, rather than the package in which it is
* declared. Set from {@link android.R.attr#externalService} attribute.
- * @hide
*/
public static final int FLAG_EXTERNAL_SERVICE = 0x0004;
diff --git a/wifi/java/android/net/wifi/ScanInfo.aidl b/core/java/android/net/DataUsageRequest.aidl
similarity index 83%
copy from wifi/java/android/net/wifi/ScanInfo.aidl
copy to core/java/android/net/DataUsageRequest.aidl
index 18ae508..d1937c7 100644
--- a/wifi/java/android/net/wifi/ScanInfo.aidl
+++ b/core/java/android/net/DataUsageRequest.aidl
@@ -1,5 +1,5 @@
/**
- * Copyright (c) 2015, The Android Open Source Project
+ * Copyright (c) 2016, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.net.wifi;
+package android.net;
-parcelable ScanInfo;
+parcelable DataUsageRequest;
diff --git a/core/java/android/net/DataUsageRequest.java b/core/java/android/net/DataUsageRequest.java
new file mode 100644
index 0000000..0e46f4c
--- /dev/null
+++ b/core/java/android/net/DataUsageRequest.java
@@ -0,0 +1,143 @@
+/**
+ * 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.net;
+
+import android.net.NetworkTemplate;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Defines a request to register a callbacks. Used to be notified on data usage via
+ * {@link android.app.usage.NetworkStatsManager#registerDataUsageCallback}.
+ * If no {@code uid}s are set, callbacks are restricted to device-owners,
+ * carrier-privileged apps, or system apps.
+ */
+public class DataUsageRequest implements Parcelable {
+
+ /**
+ * @hide
+ */
+ public static final int REQUEST_ID_UNSET = 0;
+
+ /**
+ * Identifies the request. {@link DataUsageRequest}s should only be constructed by
+ * the Framework and it is used internally to identify the request.
+ * @hide
+ */
+ public final int requestId;
+
+ /**
+ * Set of {@link NetworkTemplate}s describing the networks to monitor.
+ * @hide
+ */
+ public final NetworkTemplate[] templates;
+
+ /**
+ * Set of UIDs of which to monitor data usage.
+ *
+ * <p>If not {@code null}, the caller will be notified when any of the uids exceed
+ * the given threshold. If {@code null} all uids for which the calling process has access
+ * to stats will be monitored.
+ * @hide
+ */
+ public final int[] uids;
+
+ /**
+ * Threshold in bytes to be notified on.
+ * @hide
+ */
+ public final long thresholdInBytes;
+
+ /**
+ * @hide
+ */
+ public DataUsageRequest(int requestId, NetworkTemplate[] templates, int[] uids,
+ long thresholdInBytes) {
+ this.requestId = requestId;
+ this.templates = templates;
+ this.uids = uids;
+ this.thresholdInBytes = thresholdInBytes;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(requestId);
+ dest.writeTypedArray(templates, flags);
+ dest.writeIntArray(uids);
+ dest.writeLong(thresholdInBytes);
+ }
+
+ public static final Creator<DataUsageRequest> CREATOR =
+ new Creator<DataUsageRequest>() {
+ @Override
+ public DataUsageRequest createFromParcel(Parcel in) {
+ int requestId = in.readInt();
+ NetworkTemplate[] templates = in.createTypedArray(NetworkTemplate.CREATOR);
+ int[] uids = in.createIntArray();
+ long thresholdInBytes = in.readLong();
+ DataUsageRequest result = new DataUsageRequest(requestId,
+ templates, uids, thresholdInBytes);
+ return result;
+ }
+
+ @Override
+ public DataUsageRequest[] newArray(int size) {
+ return new DataUsageRequest[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "DataUsageRequest [ requestId=" + requestId
+ + ", networkTemplates=" + Arrays.toString(templates)
+ + ", uids=" + Arrays.toString(uids)
+ + ", thresholdInBytes=" + thresholdInBytes + " ]";
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof DataUsageRequest == false) return false;
+ DataUsageRequest that = (DataUsageRequest) obj;
+ return that.requestId == this.requestId
+ && Arrays.deepEquals(that.templates, this.templates)
+ && Arrays.equals(that.uids, this.uids)
+ && that.thresholdInBytes == this.thresholdInBytes;
+ }
+
+ @Override
+ public int hashCode() {
+ // Start with a non-zero constant.
+ int result = 17;
+
+ // Include a hash for each field.
+ result = 31 * result + requestId;
+ result = 31 * result + Arrays.deepHashCode(templates);
+ result = 31 * result + Arrays.hashCode(uids);
+ result = 31 * result + (int) (thresholdInBytes ^ (thresholdInBytes >>> 32));
+
+ return result;
+ }
+
+}
diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java
index 54d1090..fc804e5 100644
--- a/core/java/android/os/ShellCommand.java
+++ b/core/java/android/os/ShellCommand.java
@@ -234,6 +234,16 @@
}
}
+ public String peekNextArg() {
+ if (mCurArgData != null) {
+ return mCurArgData;
+ } else if (mArgPos < mArgs.length) {
+ return mArgs[mArgPos];
+ } else {
+ return null;
+ }
+ }
+
/**
* Return the next argument on the command line, whatever it is; if there are
* no arguments left, throws an IllegalArgumentException to report this to the user.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 5535eaa..dfc8601 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -670,14 +670,14 @@
"android.settings.IGNORE_BATTERY_OPTIMIZATION_SETTINGS";
/**
- * Activity Action: Ask the user to allow an to ignore battery optimizations (that is,
+ * Activity Action: Ask the user to allow an app to ignore battery optimizations (that is,
* put them on the whitelist of apps shown by
* {@link #ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS}). For an app to use this, it also
* must hold the {@link android.Manifest.permission#REQUEST_IGNORE_BATTERY_OPTIMIZATIONS}
* permission.
* <p><b>Note:</b> most applications should <em>not</em> use this; there are many facilities
* provided by the platform for applications to operate correctly in the various power
- * saving mode. This is only for unusual applications that need to deeply control their own
+ * saving modes. This is only for unusual applications that need to deeply control their own
* execution, at the potential expense of the user's battery life. Note that these applications
* greatly run the risk of showing to the user as high power consumers on their device.</p>
* <p>
@@ -695,6 +695,24 @@
"android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
/**
+ * Activity Action: Show screen for controlling which apps can ignore background data
+ * restrictions.
+ * <p>
+ * Input: if the Intent's data URI is set with an application name (using the "package" schema,
+ * like "package:com.my.app"), then when the screen is displayed it will focus on such app. If
+ * the data is not set, it will just open the screen.
+ * <p>
+ * Output: Nothing.
+ * <p>
+ * Applications can also use {@link android.net.ConnectivityManager#getRestrictBackgroundStatus
+ * ConnectivityManager#getRestrictBackgroundStatus()} to determine the status of the background
+ * data restrictions for them.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS =
+ "android.settings.IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS";
+
+ /**
* @hide
* Activity Action: Show the "app ops" settings screen.
* <p>
diff --git a/core/java/android/text/BidiFormatter.java b/core/java/android/text/BidiFormatter.java
index a535480..675803c 100644
--- a/core/java/android/text/BidiFormatter.java
+++ b/core/java/android/text/BidiFormatter.java
@@ -293,7 +293,7 @@
* directionality is determined by scanning the end of the string, the overall directionality is
* given explicitly by a heuristic to estimate the {@code str}'s directionality.
*
- * @param str String after which the mark may need to appear.
+ * @param str CharSequence after which the mark may need to appear.
* @param heuristic The text direction heuristic that will be used to estimate the {@code str}'s
* directionality.
* @return LRM for RTL text in LTR context; RLM for LTR text in RTL context;
@@ -301,7 +301,7 @@
*
* @hide
*/
- public String markAfter(String str, TextDirectionHeuristic heuristic) {
+ public String markAfter(CharSequence str, TextDirectionHeuristic heuristic) {
final boolean isRtl = heuristic.isRtl(str, 0, str.length());
// getExitDir() is called only if needed (short-circuit).
if (!mIsRtlContext && (isRtl || getExitDir(str) == DIR_RTL)) {
@@ -322,7 +322,7 @@
* entry directionality is determined by scanning the beginning of the string, the overall
* directionality is given explicitly by a heuristic to estimate the {@code str}'s directionality.
*
- * @param str String before which the mark may need to appear.
+ * @param str CharSequence before which the mark may need to appear.
* @param heuristic The text direction heuristic that will be used to estimate the {@code str}'s
* directionality.
* @return LRM for RTL text in LTR context; RLM for LTR text in RTL context;
@@ -330,7 +330,7 @@
*
* @hide
*/
- public String markBefore(String str, TextDirectionHeuristic heuristic) {
+ public String markBefore(CharSequence str, TextDirectionHeuristic heuristic) {
final boolean isRtl = heuristic.isRtl(str, 0, str.length());
// getEntryDir() is called only if needed (short-circuit).
if (!mIsRtlContext && (isRtl || getEntryDir(str) == DIR_RTL)) {
@@ -350,6 +350,13 @@
* false.
*/
public boolean isRtl(String str) {
+ return isRtl((CharSequence) str);
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isRtl(CharSequence str) {
return mDefaultTextDirectionHeuristic.isRtl(str, 0, str.length());
}
@@ -384,9 +391,16 @@
* {@code null}.
*/
public String unicodeWrap(String str, TextDirectionHeuristic heuristic, boolean isolate) {
+ return unicodeWrap((CharSequence) str, heuristic, isolate).toString();
+ }
+
+ /**
+ * @hide
+ */
+ public CharSequence unicodeWrap(CharSequence str, TextDirectionHeuristic heuristic, boolean isolate) {
if (str == null) return null;
final boolean isRtl = heuristic.isRtl(str, 0, str.length());
- StringBuilder result = new StringBuilder();
+ SpannableStringBuilder result = new SpannableStringBuilder();
if (getStereoReset() && isolate) {
result.append(markBefore(str,
isRtl ? TextDirectionHeuristics.RTL : TextDirectionHeuristics.LTR));
@@ -402,7 +416,7 @@
result.append(markAfter(str,
isRtl ? TextDirectionHeuristics.RTL : TextDirectionHeuristics.LTR));
}
- return result.toString();
+ return result;
}
/**
@@ -419,6 +433,14 @@
}
/**
+ * @hide
+ */
+ public CharSequence unicodeWrap(CharSequence str, TextDirectionHeuristic heuristic) {
+ return unicodeWrap(str, heuristic, true /* isolate */);
+ }
+
+
+ /**
* Operates like {@link #unicodeWrap(String, TextDirectionHeuristic, boolean)}, but uses the
* formatter's default direction estimation algorithm.
*
@@ -432,6 +454,13 @@
}
/**
+ * @hide
+ */
+ public CharSequence unicodeWrap(CharSequence str, boolean isolate) {
+ return unicodeWrap(str, mDefaultTextDirectionHeuristic, isolate);
+ }
+
+ /**
* Operates like {@link #unicodeWrap(String, TextDirectionHeuristic, boolean)}, but uses the
* formatter's default direction estimation algorithm and assumes {@code isolate} is true.
*
@@ -442,6 +471,13 @@
return unicodeWrap(str, mDefaultTextDirectionHeuristic, true /* isolate */);
}
+ /**
+ * @hide
+ */
+ public CharSequence unicodeWrap(CharSequence str) {
+ return unicodeWrap(str, mDefaultTextDirectionHeuristic, true /* isolate */);
+ }
+
private static BidiFormatter getDefaultInstanceFromContext(boolean isRtlContext) {
return isRtlContext ? DEFAULT_RTL_INSTANCE : DEFAULT_LTR_INSTANCE;
}
@@ -477,7 +513,7 @@
*
* @param str the string to check.
*/
- private static int getExitDir(String str) {
+ private static int getExitDir(CharSequence str) {
return new DirectionalityEstimator(str, false /* isHtml */).getExitDir();
}
@@ -494,7 +530,7 @@
*
* @param str the string to check.
*/
- private static int getEntryDir(String str) {
+ private static int getEntryDir(CharSequence str) {
return new DirectionalityEstimator(str, false /* isHtml */).getEntryDir();
}
@@ -532,7 +568,7 @@
/**
* The text to be scanned.
*/
- private final String text;
+ private final CharSequence text;
/**
* Whether the text to be scanned is to be treated as HTML, i.e. skipping over tags and
@@ -565,7 +601,7 @@
* @param isHtml Whether the text to be scanned is to be treated as HTML, i.e. skipping over
* tags and entities.
*/
- DirectionalityEstimator(String text, boolean isHtml) {
+ DirectionalityEstimator(CharSequence text, boolean isHtml) {
this.text = text;
this.isHtml = isHtml;
length = text.length();
@@ -896,4 +932,4 @@
return Character.DIRECTIONALITY_OTHER_NEUTRALS;
}
}
-}
\ No newline at end of file
+}
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index 501c6e9..b9a7421 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -21,7 +21,6 @@
import android.graphics.Rect;
import android.util.AttributeSet;
import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.RemoteViews;
import android.widget.TextView;
@@ -35,7 +34,7 @@
@RemoteViews.RemoteView
public class NotificationHeaderView extends ViewGroup {
public static final int NO_COLOR = -1;
- private final int mHeaderMinWidth;
+ private final int mChildMinWidth;
private final int mExpandTopPadding;
private final int mContentEndMargin;
private View mAppName;
@@ -46,6 +45,7 @@
private View mIcon;
private TextView mChildCount;
private View mProfileBadge;
+ private View mInfo;
private int mIconColor;
private int mOriginalNotificationColor;
private boolean mGroupHeader;
@@ -66,7 +66,7 @@
public NotificationHeaderView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- mHeaderMinWidth = getResources().getDimensionPixelSize(
+ mChildMinWidth = getResources().getDimensionPixelSize(
com.android.internal.R.dimen.notification_header_shrink_min_width);
mContentEndMargin = getResources().getDimensionPixelSize(
com.android.internal.R.dimen.notification_content_margin_end);
@@ -82,6 +82,7 @@
mIcon = findViewById(com.android.internal.R.id.icon);
mChildCount = (TextView) findViewById(com.android.internal.R.id.number_of_children);
mProfileBadge = findViewById(com.android.internal.R.id.profile_badge);
+ mInfo = findViewById(com.android.internal.R.id.header_content_info);
}
@Override
@@ -109,14 +110,23 @@
}
if (totalWidth > givenWidth) {
int overFlow = totalWidth - givenWidth;
- // We are overflowing, lets shrink
+ // We are overflowing, lets shrink the info first
+ final int infoWidth = mInfo.getMeasuredWidth();
+ if (mInfo.getVisibility() != GONE && infoWidth > mChildMinWidth) {
+ int newSize = infoWidth - Math.min(infoWidth - mChildMinWidth, overFlow);
+ int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
+ mInfo.measure(childWidthSpec, wrapContentHeightSpec);
+ overFlow -= infoWidth - newSize;
+ }
+ // still overflowing, lets shrink the app name now
final int appWidth = mAppName.getMeasuredWidth();
- if (mAppName.getVisibility() != GONE && appWidth > mHeaderMinWidth) {
- int newSize = appWidth - Math.min(appWidth - mHeaderMinWidth, overFlow);
+ if (overFlow > 0 && mAppName.getVisibility() != GONE && appWidth > mChildMinWidth) {
+ int newSize = appWidth - Math.min(appWidth - mChildMinWidth, overFlow);
int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
mAppName.measure(childWidthSpec, wrapContentHeightSpec);
overFlow -= appWidth - newSize;
}
+ // still overflowing, finaly we shrink the subtext
if (overFlow > 0 && mSubTextView.getVisibility() != GONE) {
// we're still too big
final int subTextWidth = mSubTextView.getMeasuredWidth();
@@ -124,12 +134,8 @@
int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
mSubTextView.measure(childWidthSpec, wrapContentHeightSpec);
}
- totalWidth = givenWidth;
}
- if (mProfileBadge.getVisibility() != View.GONE) {
- totalWidth = givenWidth;
- }
- setMeasuredDimension(totalWidth, givenHeight);
+ setMeasuredDimension(givenWidth, givenHeight);
}
@Override
@@ -275,18 +281,16 @@
mTouchRects.clear();
addRectAroundViewView(mIcon);
addRectAroundViewView(mExpandButton);
- addInBetweenRect();
+ addWidthRect();
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
- private void addInBetweenRect() {
- final Rect r = new Rect();
+ private void addWidthRect() {
+ Rect r = new Rect();
r.top = 0;
r.bottom = (int) (32 * getResources().getDisplayMetrics().density);
- Rect leftRect = mTouchRects.get(0);
- r.left = leftRect.right;
- Rect rightRect = mTouchRects.get(1);
- r.right = rightRect.left;
+ r.left = 0;
+ r.right = getWidth();
mTouchRects.add(r);
}
diff --git a/core/java/android/view/RemotableViewMethod.java b/core/java/android/view/RemotableViewMethod.java
index 4318290..e5cae84 100644
--- a/core/java/android/view/RemotableViewMethod.java
+++ b/core/java/android/view/RemotableViewMethod.java
@@ -29,6 +29,12 @@
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface RemotableViewMethod {
+ /**
+ * @return Method name which can be called on a background thread. It should have the
+ * same arguments as the original method and should return a {@link Runnable} (or null)
+ * which will be called on the UI thread.
+ */
+ String asyncImpl() default "";
}
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index 2aace0f..7017ff5 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -136,6 +136,9 @@
private RenderNode(String name, View owningView) {
mNativeRenderNode = nCreate(name);
mOwningView = owningView;
+ if (mOwningView instanceof SurfaceView) {
+ nRequestPositionUpdates(mNativeRenderNode, (SurfaceView) mOwningView);
+ }
}
/**
@@ -863,6 +866,8 @@
private static native void nOutput(long renderNode);
private static native int nGetDebugSize(long renderNode);
+ private static native void nRequestPositionUpdates(long renderNode, SurfaceView callback);
+
///////////////////////////////////////////////////////////////////////////
// Animations
///////////////////////////////////////////////////////////////////////////
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 5b48e28..a296051 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -135,7 +135,7 @@
}
};
- final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener
+ private final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener
= new ViewTreeObserver.OnScrollChangedListener() {
@Override
public void onScrollChanged() {
@@ -143,6 +143,17 @@
}
};
+ private final ViewTreeObserver.OnPreDrawListener mDrawListener =
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ // reposition ourselves where the surface is
+ mHaveFrame = getWidth() > 0 && getHeight() > 0;
+ updateWindow(false, false);
+ return true;
+ }
+ };
+
boolean mRequestedVisible = false;
boolean mWindowVisibility = false;
boolean mViewVisibility = false;
@@ -168,17 +179,9 @@
boolean mUpdateWindowNeeded;
boolean mReportDrawNeeded;
private Translator mTranslator;
+ private int mWindowInsetLeft;
+ private int mWindowInsetTop;
- private final ViewTreeObserver.OnPreDrawListener mDrawListener =
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- // reposition ourselves where the surface is
- mHaveFrame = getWidth() > 0 && getHeight() > 0;
- updateWindow(false, false);
- return true;
- }
- };
private boolean mGlobalListenersAdded;
public SurfaceView(Context context) {
@@ -443,17 +446,17 @@
int myHeight = mRequestedHeight;
if (myHeight <= 0) myHeight = getHeight();
- getLocationInWindow(mLocation);
final boolean creating = mWindow == null;
final boolean formatChanged = mFormat != mRequestedFormat;
final boolean sizeChanged = mWindowSpaceWidth != myWidth || mWindowSpaceHeight != myHeight;
final boolean visibleChanged = mVisible != mRequestedVisible;
final boolean layoutSizeChanged = getWidth() != mLayout.width
|| getHeight() != mLayout.height;
- final boolean positionChanged = mWindowSpaceLeft != mLocation[0] || mWindowSpaceTop != mLocation[1];
if (force || creating || formatChanged || sizeChanged || visibleChanged
|| mUpdateWindowNeeded || mReportDrawNeeded || redrawNeeded) {
+ getLocationInWindow(mLocation);
+
if (DEBUG) Log.i(TAG, "Changes: creating=" + creating
+ " format=" + formatChanged + " size=" + sizeChanged
+ " visible=" + visibleChanged
@@ -643,27 +646,69 @@
TAG, "Layout: x=" + mLayout.x + " y=" + mLayout.y +
" w=" + mLayout.width + " h=" + mLayout.height +
", frame=" + mSurfaceFrame);
- } else if (positionChanged || layoutSizeChanged) { // Only the position has changed
- mWindowSpaceLeft = mLocation[0];
- mWindowSpaceTop = mLocation[1];
- // For our size changed check, we keep mLayout.width and mLayout.height
- // in view local space.
- mLocation[0] = mLayout.width = getWidth();
- mLocation[1] = mLayout.height = getHeight();
+ } else if (!isHardwareAccelerated()) {
+ getLocationInWindow(mLocation);
+ final boolean positionChanged = mWindowSpaceLeft != mLocation[0]
+ || mWindowSpaceTop != mLocation[1];
+ if (positionChanged || layoutSizeChanged) { // Only the position has changed
+ mWindowSpaceLeft = mLocation[0];
+ mWindowSpaceTop = mLocation[1];
+ // For our size changed check, we keep mLayout.width and mLayout.height
+ // in view local space.
+ mLocation[0] = mLayout.width = getWidth();
+ mLocation[1] = mLayout.height = getHeight();
- transformFromViewToWindowSpace(mLocation);
+ transformFromViewToWindowSpace(mLocation);
- try {
- mSession.repositionChild(mWindow, mWindowSpaceLeft, mWindowSpaceTop,
- mLocation[0], mLocation[1],
- viewRoot != null ? viewRoot.getNextFrameNumber() : -1,
- mWinFrame);
- } catch (RemoteException ex) {
- Log.e(TAG, "Exception from relayout", ex);
+ try {
+ Log.d(TAG, String.format("updateWindowPosition UI, " +
+ "postion = [%d, %d, %d, %d]", mWindowSpaceLeft, mWindowSpaceTop,
+ mLocation[0], mLocation[1]));
+ mSession.repositionChild(mWindow, mWindowSpaceLeft, mWindowSpaceTop,
+ mLocation[0], mLocation[1], -1, mWinFrame);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Exception from relayout", ex);
+ }
}
}
}
+ private Rect mRTLastReportedPosition = new Rect();
+
+ /**
+ * Called by native on RenderThread to update the window position
+ * @hide
+ */
+ public final void updateWindowPositionRT(long frameNumber,
+ int left, int top, int right, int bottom) {
+ IWindowSession session = mSession;
+ MyWindow window = mWindow;
+ if (session == null || window == null) {
+ // Guess we got detached, that sucks
+ return;
+ }
+ if (mRTLastReportedPosition.left == left
+ && mRTLastReportedPosition.top == top
+ && mRTLastReportedPosition.right == right
+ && mRTLastReportedPosition.bottom == bottom) {
+ return;
+ }
+ try {
+ if (DEBUG) {
+ Log.d(TAG, String.format("updateWindowPosition RT, frameNr = %d, " +
+ "postion = [%d, %d, %d, %d]", frameNumber, left, top,
+ right, bottom));
+ }
+ // Just using mRTLastReportedPosition as a dummy rect here
+ session.repositionChild(window, left, top, right, bottom, frameNumber,
+ mRTLastReportedPosition);
+ // Now overwrite mRTLastReportedPosition with our values
+ mRTLastReportedPosition.set(left, top, right, bottom);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Exception from repositionChild", ex);
+ }
+ }
+
private SurfaceHolder.Callback[] getSurfaceCallbacks() {
SurfaceHolder.Callback callbacks[];
synchronized (mCallbacks) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 98e3289..97e0143 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -6815,20 +6815,6 @@
}
}
- long getNextFrameNumber() {
- long frameNumber = -1;
- if (mSurfaceHolder != null) {
- mSurfaceHolder.mSurfaceLock.lock();
- }
- if (mSurface.isValid()) {
- frameNumber = mSurface.getNextFrameNumber();
- }
- if (mSurfaceHolder != null) {
- mSurfaceHolder.mSurfaceLock.unlock();
- }
- return frameNumber;
- }
-
class TakenSurfaceHolder extends BaseSurfaceHolder {
@Override
public boolean onAllowLockCanvas() {
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index 6e90baf..f601f7d 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -392,6 +392,26 @@
return mDrawable;
}
+ private class ImageDrawableCallback implements Runnable {
+
+ private final Drawable drawable;
+ private final Uri uri;
+ private final int resource;
+
+ ImageDrawableCallback(Drawable drawable, Uri uri, int resource) {
+ this.drawable = drawable;
+ this.uri = uri;
+ this.resource = resource;
+ }
+
+ @Override
+ public void run() {
+ setImageDrawable(drawable);
+ mUri = uri;
+ mResource = resource;
+ }
+ }
+
/**
* Sets a drawable as the content of this ImageView.
*
@@ -405,7 +425,7 @@
*
* @attr ref android.R.styleable#ImageView_src
*/
- @android.view.RemotableViewMethod
+ @android.view.RemotableViewMethod(asyncImpl="setImageResourceAsync")
public void setImageResource(@DrawableRes int resId) {
// The resource configuration may have changed, so we should always
// try to load the resource even if the resId hasn't changed.
@@ -424,6 +444,11 @@
invalidate();
}
+ /** @hide **/
+ public Runnable setImageResourceAsync(@DrawableRes int resId) {
+ return new ImageDrawableCallback(getContext().getDrawable(resId), null, resId);
+ }
+
/**
* Sets the content of this ImageView to the specified Uri.
*
@@ -435,7 +460,7 @@
*
* @param uri the Uri of an image, or {@code null} to clear the content
*/
- @android.view.RemotableViewMethod
+ @android.view.RemotableViewMethod(asyncImpl="setImageURIAsync")
public void setImageURI(@Nullable Uri uri) {
if (mResource != 0 || (mUri != uri && (uri == null || mUri == null || !uri.equals(mUri)))) {
updateDrawable(null);
@@ -454,6 +479,19 @@
}
}
+ /** @hide **/
+ public Runnable setImageURIAsync(@Nullable Uri uri) {
+ if (mResource != 0 || (mUri != uri && (uri == null || mUri == null || !uri.equals(mUri)))) {
+ Drawable d = uri == null ? null : getDrawableFromUri(uri);
+ if (d == null) {
+ // Do not set the URI if the drawable couldn't be loaded.
+ uri = null;
+ }
+ return new ImageDrawableCallback(d, uri, 0);
+ }
+ return null;
+ }
+
/**
* Sets a drawable as the content of this ImageView.
*
@@ -490,11 +528,16 @@
* @param icon an Icon holding the desired image, or {@code null} to clear
* the content
*/
- @android.view.RemotableViewMethod
+ @android.view.RemotableViewMethod(asyncImpl="setImageIconAsync")
public void setImageIcon(@Nullable Icon icon) {
setImageDrawable(icon == null ? null : icon.loadDrawable(mContext));
}
+ /** @hide **/
+ public Runnable setImageIconAsync(@Nullable Icon icon) {
+ return new ImageDrawableCallback(icon == null ? null : icon.loadDrawable(mContext), null, 0);
+ }
+
/**
* Applies a tint to the image drawable. Does not modify the current tint
* mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
@@ -786,8 +829,7 @@
return;
}
- final Resources res = getResources();
- if (res == null) {
+ if (getResources() == null) {
return;
}
@@ -802,37 +844,7 @@
mUri = null;
}
} else if (mUri != null) {
- final String scheme = mUri.getScheme();
- if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) {
- try {
- // Load drawable through Resources, to get the source density information
- ContentResolver.OpenResourceIdResult r =
- mContext.getContentResolver().getResourceId(mUri);
- d = r.r.getDrawable(r.id, mContext.getTheme());
- } catch (Exception e) {
- Log.w(LOG_TAG, "Unable to open content: " + mUri, e);
- }
- } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)
- || ContentResolver.SCHEME_FILE.equals(scheme)) {
- InputStream stream = null;
- try {
- stream = mContext.getContentResolver().openInputStream(mUri);
- d = Drawable.createFromResourceStream(
- mUseCorrectStreamDensity ? res : null, null, stream, null);
- } catch (Exception e) {
- Log.w(LOG_TAG, "Unable to open content: " + mUri, e);
- } finally {
- if (stream != null) {
- try {
- stream.close();
- } catch (IOException e) {
- Log.w(LOG_TAG, "Unable to close content: " + mUri, e);
- }
- }
- }
- } else {
- d = Drawable.createFromPath(mUri.toString());
- }
+ d = getDrawableFromUri(mUri);
if (d == null) {
Log.w(LOG_TAG, "resolveUri failed on bad bitmap uri: " + mUri);
@@ -846,6 +858,41 @@
updateDrawable(d);
}
+ private Drawable getDrawableFromUri(Uri uri) {
+ final String scheme = uri.getScheme();
+ if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) {
+ try {
+ // Load drawable through Resources, to get the source density information
+ ContentResolver.OpenResourceIdResult r =
+ mContext.getContentResolver().getResourceId(uri);
+ return r.r.getDrawable(r.id, mContext.getTheme());
+ } catch (Exception e) {
+ Log.w(LOG_TAG, "Unable to open content: " + uri, e);
+ }
+ } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)
+ || ContentResolver.SCHEME_FILE.equals(scheme)) {
+ InputStream stream = null;
+ try {
+ stream = mContext.getContentResolver().openInputStream(uri);
+ return Drawable.createFromResourceStream(
+ mUseCorrectStreamDensity ? getResources() : null, null, stream, null);
+ } catch (Exception e) {
+ Log.w(LOG_TAG, "Unable to open content: " + uri, e);
+ } finally {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ Log.w(LOG_TAG, "Unable to close content: " + uri, e);
+ }
+ }
+ }
+ } else {
+ return Drawable.createFromPath(uri.toString());
+ }
+ return null;
+ }
+
@Override
public int[] onCreateDrawableState(int extraSpace) {
if (mState == null) {
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 584df08..8fa71a2 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -43,6 +43,7 @@
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
+import android.view.View.OnAttachStateChangeListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.ViewParent;
@@ -164,7 +165,20 @@
com.android.internal.R.attr.state_above_anchor
};
+ private final OnAttachStateChangeListener mOnAnchorRootDetachedListener =
+ new OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {}
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ mIsAnchorRootAttached = false;
+ }
+ };
+
private WeakReference<View> mAnchor;
+ private WeakReference<View> mAnchorRoot;
+ private boolean mIsAnchorRootAttached;
private final OnScrollChangedListener mOnScrollChangedListener = new OnScrollChangedListener() {
@Override
@@ -1037,7 +1051,7 @@
TransitionManager.endTransitions(mDecorView);
- unregisterForScrollChanged();
+ unregisterForViewTreeChanges();
mIsShowing = true;
mIsDropdown = false;
@@ -1120,7 +1134,7 @@
TransitionManager.endTransitions(mDecorView);
- registerForScrollChanged(anchor, xoff, yoff, gravity);
+ registerForViewTreeChanges(anchor, xoff, yoff, gravity);
mIsShowing = true;
mIsDropdown = true;
@@ -1633,14 +1647,23 @@
mIsShowing = false;
mIsTransitioningToDismiss = true;
+ // This method may be called as part of window detachment, in which
+ // case the anchor view (and its root) will still return true from
+ // isAttachedToWindow() during execution of this method; however, we
+ // can expect the OnAttachStateChangeListener to have been called prior
+ // to executing this method, so we can rely on that instead.
final Transition exitTransition = mExitTransition;
- if (exitTransition != null && decorView.isLaidOut()) {
+ if (!mIsAnchorRootAttached && exitTransition != null && decorView.isLaidOut()) {
// The decor view is non-interactive during exit transitions.
final LayoutParams p = (LayoutParams) decorView.getLayoutParams();
p.flags |= LayoutParams.FLAG_NOT_TOUCHABLE;
p.flags |= LayoutParams.FLAG_NOT_FOCUSABLE;
mWindowManager.updateViewLayout(decorView, p);
+ // Once we start dismissing the decor view, all state (including
+ // the anchor root) needs to be moved to the decor view since we
+ // may open another popup while it's busy exiting.
+ final View anchorRoot = mAnchorRoot != null ? mAnchorRoot.get() : null;
final Rect epicenter = getTransitionEpicenter();
exitTransition.setEpicenterCallback(new EpicenterCallback() {
@Override
@@ -1648,18 +1671,19 @@
return epicenter;
}
});
- decorView.startExitTransition(exitTransition, new TransitionListenerAdapter() {
- @Override
- public void onTransitionEnd(Transition transition) {
- dismissImmediate(decorView, contentHolder, contentView);
- }
- });
+ decorView.startExitTransition(exitTransition, anchorRoot,
+ new TransitionListenerAdapter() {
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ dismissImmediate(decorView, contentHolder, contentView);
+ }
+ });
} else {
dismissImmediate(decorView, contentHolder, contentView);
}
// Clears the anchor view.
- unregisterForScrollChanged();
+ unregisterForViewTreeChanges();
if (mOnDismissListener != null) {
mOnDismissListener.onDismiss();
@@ -1925,7 +1949,7 @@
final WeakReference<View> oldAnchor = mAnchor;
final boolean needsUpdate = updateLocation && (mAnchorXoff != xoff || mAnchorYoff != yoff);
if (oldAnchor == null || oldAnchor.get() != anchor || (needsUpdate && !mIsDropdown)) {
- registerForScrollChanged(anchor, xoff, yoff, mAnchoredGravity);
+ registerForViewTreeChanges(anchor, xoff, yoff, mAnchoredGravity);
} else if (needsUpdate) {
// No need to register again if this is a DropDown, showAsDropDown already did.
mAnchorXoff = xoff;
@@ -1969,27 +1993,38 @@
public void onDismiss();
}
- private void unregisterForScrollChanged() {
- final WeakReference<View> anchorRef = mAnchor;
- final View anchor = anchorRef == null ? null : anchorRef.get();
+ private void unregisterForViewTreeChanges() {
+ final View anchor = mAnchor != null ? mAnchor.get() : null;
if (anchor != null) {
final ViewTreeObserver vto = anchor.getViewTreeObserver();
vto.removeOnScrollChangedListener(mOnScrollChangedListener);
}
+ final View anchorRoot = mAnchorRoot != null ? mAnchorRoot.get() : null;
+ if (anchorRoot != null) {
+ anchorRoot.removeOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
+ }
+
mAnchor = null;
+ mAnchorRoot = null;
+ mIsAnchorRootAttached = false;
}
- private void registerForScrollChanged(View anchor, int xoff, int yoff, int gravity) {
- unregisterForScrollChanged();
-
- mAnchor = new WeakReference<>(anchor);
+ private void registerForViewTreeChanges(View anchor, int xoff, int yoff, int gravity) {
+ unregisterForViewTreeChanges();
final ViewTreeObserver vto = anchor.getViewTreeObserver();
if (vto != null) {
vto.addOnScrollChangedListener(mOnScrollChangedListener);
}
+ final View anchorRoot = anchor.getRootView();
+ anchorRoot.addOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
+
+ mAnchor = new WeakReference<>(anchor);
+ mAnchorRoot = new WeakReference<>(anchorRoot);
+ mIsAnchorRootAttached = anchorRoot.isAttachedToWindow();
+
mAnchorXoff = xoff;
mAnchorYoff = yoff;
mAnchoredGravity = gravity;
@@ -2109,16 +2144,23 @@
* its {@code onTransitionEnd} method called even if the transition
* never starts; however, it may be called with a {@code null} argument.
*/
- public void startExitTransition(Transition transition, final TransitionListener listener) {
+ public void startExitTransition(Transition transition, final View anchorRoot,
+ final TransitionListener listener) {
if (transition == null) {
return;
}
+ // The anchor view's window may go away while we're executing our
+ // transition, in which case we need to end the transition
+ // immediately and execute the listener to remove the popup.
+ anchorRoot.addOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
+
// The exit listener MUST be called for cleanup, even if the
// transition never starts or ends. Stash it for later.
mPendingExitListener = new TransitionListenerAdapter() {
@Override
public void onTransitionEnd(Transition transition) {
+ anchorRoot.removeOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
listener.onTransitionEnd(transition);
// The listener was called. Our job here is done.
@@ -2153,6 +2195,19 @@
mPendingExitListener.onTransitionEnd(null);
}
}
+
+ private final OnAttachStateChangeListener mOnAnchorRootDetachedListener =
+ new OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {}
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ v.removeOnAttachStateChangeListener(this);
+
+ TransitionManager.endTransitions(PopupDecorView.this);
+ }
+ };
}
private class PopupBackgroundView extends FrameLayout {
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 0dd803a2..dee25d3 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -39,8 +39,10 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
+import android.os.CancellationSignal;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.StrictMode;
@@ -66,6 +68,7 @@
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.concurrent.Executor;
/**
* A class that describes a view hierarchy that can be displayed in
@@ -146,6 +149,8 @@
private static final Object[] sMethodsLock = new Object[0];
private static final ArrayMap<Class<? extends View>, ArrayMap<MutablePair<String, Class<?>>, Method>> sMethods =
new ArrayMap<Class<? extends View>, ArrayMap<MutablePair<String, Class<?>>, Method>>();
+ private static final ArrayMap<Method, Method> sAsyncMethods = new ArrayMap<>();
+
private static final ThreadLocal<Object[]> sInvokeArgsTls = new ThreadLocal<Object[]>() {
@Override
protected Object[] initialValue() {
@@ -293,10 +298,40 @@
return (getActionName() + viewId);
}
+ /**
+ * This is called on the background thread. It should perform any non-ui computations
+ * and return the final action which will run on the UI thread.
+ * Override this if some of the tasks can be performed async.
+ */
+ public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
+ return this;
+ }
+
int viewId;
}
/**
+ * Action class used during async inflation of RemoteViews. Subclasses are not parcelable.
+ */
+ private static abstract class RuntimeAction extends Action {
+ @Override
+ public final String getActionName() {
+ return "RuntimeAction";
+ }
+
+ @Override
+ public final void writeToParcel(Parcel dest, int flags) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ // Constant used during async execution. It is not parcelable.
+ private static final Action ACTION_NOOP = new RuntimeAction() {
+ @Override
+ public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { }
+ };
+
+ /**
* Merges the passed RemoteViews actions with this RemoteViews actions according to
* action-specific merge rules.
*
@@ -810,6 +845,36 @@
return method;
}
+ /**
+ * @return the async implementation of the provided method.
+ */
+ private Method getAsyncMethod(Method method) {
+ synchronized (sAsyncMethods) {
+ int valueIndex = sAsyncMethods.indexOfKey(method);
+ if (valueIndex >= 0) {
+ return sAsyncMethods.valueAt(valueIndex);
+ }
+
+ RemotableViewMethod annotation = method.getAnnotation(RemotableViewMethod.class);
+ Method asyncMethod = null;
+ if (!annotation.asyncImpl().isEmpty()) {
+ try {
+ asyncMethod = method.getDeclaringClass()
+ .getMethod(annotation.asyncImpl(), method.getParameterTypes());
+ if (!asyncMethod.getReturnType().equals(Runnable.class)) {
+ throw new ActionException("Async implementation for " + method.getName() +
+ " does not return a Runnable");
+ }
+ } catch (NoSuchMethodException ex) {
+ throw new ActionException("Async implementation declared but not defined for " +
+ method.getName());
+ }
+ }
+ sAsyncMethods.put(method, asyncMethod);
+ return asyncMethod;
+ }
+ }
+
private static String getParameters(Class<?> paramType) {
if (paramType == null) return "()";
return "(" + paramType + ")";
@@ -1324,6 +1389,37 @@
}
}
+ @Override
+ public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
+ final View view = root.findViewById(viewId);
+ if (view == null) return ACTION_NOOP;
+
+ Class<?> param = getParameterType();
+ if (param == null) {
+ throw new ActionException("bad type: " + this.type);
+ }
+
+ try {
+ Method method = getMethod(view, this.methodName, param);
+ Method asyncMethod = getAsyncMethod(method);
+
+ if (asyncMethod != null) {
+ Runnable endAction = (Runnable) asyncMethod.invoke(view, wrapArg(this.value));
+ if (endAction == null) {
+ return ACTION_NOOP;
+ } else {
+ return new RunnableAction(endAction);
+ }
+ }
+ } catch (ActionException e) {
+ throw e;
+ } catch (Exception ex) {
+ throw new ActionException(ex);
+ }
+
+ return this;
+ }
+
public int mergeBehavior() {
// smoothScrollBy is cumulative, everything else overwites.
if (methodName.equals("smoothScrollBy")) {
@@ -1340,6 +1436,22 @@
}
}
+ /**
+ * This is only used for async execution of actions and it not parcelable.
+ */
+ private static final class RunnableAction extends RuntimeAction {
+ private final Runnable mRunnable;
+
+ RunnableAction(Runnable r) {
+ mRunnable = r;
+ }
+
+ @Override
+ public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
+ mRunnable.run();
+ }
+ }
+
private void configureRemoteViewsAsChild(RemoteViews rv) {
mBitmapCache.assimilate(rv.mBitmapCache);
rv.setBitmapCache(mBitmapCache);
@@ -1401,6 +1513,43 @@
}
@Override
+ public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
+ // In the async implementation, update the view tree so that subsequent calls to
+ // findViewById return the currect view.
+ root.createTree();
+ ViewTree target = root.findViewTreeById(viewId);
+ if ((target == null) || !(target.mRoot instanceof ViewGroup)) {
+ return ACTION_NOOP;
+ }
+ if (nestedViews == null) {
+ // Clear all children when nested views omitted
+ target.mChildren = null;
+ return this;
+ } else {
+ // Inflate nested views and perform all the async tasks for the child remoteView.
+ final Context context = root.mRoot.getContext();
+ final AsyncApplyTask task = nestedViews.getAsyncApplyTask(
+ context, (ViewGroup) target.mRoot, null, handler);
+ final ViewTree tree = task.doInBackground();
+
+ // Update the global view tree, so that next call to findViewTreeById
+ // goes through the subtree as well.
+ target.addChild(tree);
+
+ return new RuntimeAction() {
+
+ @Override
+ public void apply(View root, ViewGroup rootParent, OnClickHandler handler) throws ActionException {
+ // This view will exist as we have already made sure
+ final ViewGroup target = (ViewGroup) root.findViewById(viewId);
+ task.onPostExecute(tree);
+ target.addView(task.mResult);
+ }
+ };
+ }
+ }
+
+ @Override
public void updateMemoryUsageEstimate(MemoryUsageCounter counter) {
if (nestedViews != null) {
counter.increment(nestedViews.estimateMemoryUsage());
@@ -1520,7 +1669,13 @@
public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
final TextView target = (TextView) root.findViewById(viewId);
if (target == null) return;
- if (useIcons) {
+ if (drawablesLoaded) {
+ if (isRelative) {
+ target.setCompoundDrawablesRelativeWithIntrinsicBounds(id1, id2, id3, id4);
+ } else {
+ target.setCompoundDrawablesWithIntrinsicBounds(id1, id2, id3, id4);
+ }
+ } else if (useIcons) {
final Context ctx = target.getContext();
final Drawable id1 = i1 == null ? null : i1.loadDrawable(ctx);
final Drawable id2 = i2 == null ? null : i2.loadDrawable(ctx);
@@ -1540,6 +1695,33 @@
}
}
+ @Override
+ public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
+ final TextView target = (TextView) root.findViewById(viewId);
+ if (target == null) return ACTION_NOOP;
+
+ TextViewDrawableAction copy = useIcons ?
+ new TextViewDrawableAction(viewId, isRelative, i1, i2, i3, i4) :
+ new TextViewDrawableAction(viewId, isRelative, d1, d2, d3, d4);
+
+ // Load the drawables on the background thread.
+ copy.drawablesLoaded = true;
+ final Context ctx = target.getContext();
+
+ if (useIcons) {
+ copy.id1 = i1 == null ? null : i1.loadDrawable(ctx);
+ copy.id2 = i2 == null ? null : i2.loadDrawable(ctx);
+ copy.id3 = i3 == null ? null : i3.loadDrawable(ctx);
+ copy.id4 = i4 == null ? null : i4.loadDrawable(ctx);
+ } else {
+ copy.id1 = d1 == 0 ? null : ctx.getDrawable(d1);
+ copy.id2 = d2 == 0 ? null : ctx.getDrawable(d2);
+ copy.id3 = d3 == 0 ? null : ctx.getDrawable(d3);
+ copy.id4 = d4 == 0 ? null : ctx.getDrawable(d4);
+ }
+ return copy;
+ }
+
public String getActionName() {
return "TextViewDrawableAction";
}
@@ -1549,6 +1731,9 @@
int d1, d2, d3, d4;
Icon i1, i2, i3, i4;
+ boolean drawablesLoaded = false;
+ Drawable id1, id2, id3, id4;
+
public final static int TAG = 11;
}
@@ -2852,7 +3037,15 @@
public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
RemoteViews rvToApply = getRemoteViewsToApply(context);
- View result;
+ View result = inflateView(context, rvToApply, parent);
+ loadTransitionOverride(context, handler);
+
+ rvToApply.performApply(result, parent, handler);
+
+ return result;
+ }
+
+ private View inflateView(Context context, RemoteViews rv, ViewGroup parent) {
// RemoteViews may be built by an application installed in another
// user. So build a context that loads resources from that user but
// still returns the current users userId so settings like data / time formats
@@ -2880,13 +3073,7 @@
// we don't add a filter to the static version returned by getSystemService.
inflater = inflater.cloneInContext(inflationContext);
inflater.setFilter(this);
- result = inflater.inflate(rvToApply.getLayoutId(), parent, false);
-
- loadTransitionOverride(context, handler);
-
- rvToApply.performApply(result, parent, handler);
-
- return result;
+ return inflater.inflate(rv.getLayoutId(), parent, false);
}
private static void loadTransitionOverride(Context context,
@@ -2908,6 +3095,143 @@
}
/**
+ * Implement this interface to receive a callback when
+ * {@link #applyAsync} or {@link #reapplyAsync} is finished.
+ * @hide
+ */
+ public interface OnViewAppliedListener {
+ void onViewApplied(View v);
+
+ void onError(Exception e);
+ }
+
+ /**
+ * Applies the views asynchronously, moving as much of the task on the background
+ * thread as possible.
+ *
+ * @see {@link #apply(Context, ViewGroup)}
+ * @param context Default context to use
+ * @param parent Parent that the resulting view hierarchy will be attached to. This method
+ * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate.
+ * @param listener the callback to run when all actions have been applied. May be null.
+ * @param executor The executor to use. If null {@link AsyncTask#THREAD_POOL_EXECUTOR} is used.
+ * @return CancellationSignal
+ * @hide
+ */
+ public CancellationSignal applyAsync(
+ Context context, ViewGroup parent, Executor executor, OnViewAppliedListener listener) {
+ return applyAsync(context, parent, executor, listener, null);
+ }
+
+ private CancellationSignal startTaskOnExecutor(AsyncApplyTask task, Executor executor) {
+ CancellationSignal cancelSignal = new CancellationSignal();
+ cancelSignal.setOnCancelListener(task);
+
+ task.executeOnExecutor(executor == null ? AsyncTask.THREAD_POOL_EXECUTOR : executor);
+ return cancelSignal;
+ }
+
+ /** @hide */
+ public CancellationSignal applyAsync(Context context, ViewGroup parent,
+ Executor executor, OnViewAppliedListener listener, OnClickHandler handler) {
+ return startTaskOnExecutor(getAsyncApplyTask(context, parent, listener, handler), executor);
+ }
+
+ private AsyncApplyTask getAsyncApplyTask(Context context, ViewGroup parent,
+ OnViewAppliedListener listener, OnClickHandler handler) {
+ return new AsyncApplyTask(getRemoteViewsToApply(context), parent, context, listener,
+ handler, null);
+ }
+
+ private class AsyncApplyTask extends AsyncTask<Void, Void, ViewTree>
+ implements CancellationSignal.OnCancelListener {
+ final RemoteViews mRV;
+ final ViewGroup mParent;
+ final Context mContext;
+ final OnViewAppliedListener mListener;
+ final OnClickHandler mHandler;
+
+ private View mResult;
+ private ViewTree mTree;
+ private Action[] mActions;
+ private Exception mError;
+
+ private AsyncApplyTask(
+ RemoteViews rv, ViewGroup parent, Context context, OnViewAppliedListener listener,
+ OnClickHandler handler, View result) {
+ mRV = rv;
+ mParent = parent;
+ mContext = context;
+ mListener = listener;
+ mHandler = handler;
+
+ mResult = result;
+ loadTransitionOverride(context, handler);
+ }
+
+ @Override
+ protected ViewTree doInBackground(Void... params) {
+ try {
+ if (mResult == null) {
+ mResult = inflateView(mContext, mRV, mParent);
+ }
+
+ mTree = new ViewTree(mResult);
+ if (mRV.mActions != null) {
+ int count = mRV.mActions.size();
+ mActions = new Action[count];
+ for (int i = 0; i < count && !isCancelled(); i++) {
+ // TODO: check if isCanclled in nested views.
+ mActions[i] = mRV.mActions.get(i).initActionAsync(mTree, mParent, mHandler);
+ }
+ } else {
+ mActions = null;
+ }
+ return mTree;
+ } catch (Exception e) {
+ mError = e;
+ return null;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(ViewTree viewTree) {
+ if (mError == null) {
+ try {
+ if (mActions != null) {
+ OnClickHandler handler = mHandler == null
+ ? DEFAULT_ON_CLICK_HANDLER : mHandler;
+ for (Action a : mActions) {
+ a.apply(viewTree.mRoot, mParent, handler);
+ }
+ }
+ } catch (Exception e) {
+ mError = e;
+ }
+ }
+
+ if (mListener != null) {
+ if (mError != null) {
+ mListener.onError(mError);
+ } else {
+ mListener.onViewApplied(viewTree.mRoot);
+ }
+ } else if (mError != null) {
+ if (mError instanceof ActionException) {
+ throw (ActionException) mError;
+ } else {
+ throw new ActionException(mError);
+ }
+ }
+ }
+
+ @Override
+ public void onCancel() {
+ cancel(true);
+ }
+ }
+
+ /**
* Applies all of the actions to the provided view.
*
* <p><strong>Caller beware: this may throw</strong>
@@ -2936,6 +3260,43 @@
rvToApply.performApply(v, (ViewGroup) v.getParent(), handler);
}
+ /**
+ * Applies all the actions to the provided view, moving as much of the task on the background
+ * thread as possible.
+ *
+ * @see {@link #reapply(Context, View)}
+ * @param context Default context to use
+ * @param v The view to apply the actions to. This should be the result of
+ * the {@link #apply(Context,ViewGroup)} call.
+ * @param listener the callback to run when all actions have been applied. May be null.
+ * @param executor The executor to use. If null {@link AsyncTask#THREAD_POOL_EXECUTOR} is used
+ * @return CancellationSignal
+ * @hide
+ */
+ public CancellationSignal reapplyAsync(
+ Context context, View v, Executor executor, OnViewAppliedListener listener) {
+ return reapplyAsync(context, v, executor, listener, null);
+ }
+
+ /** @hide */
+ public CancellationSignal reapplyAsync(Context context, View v, Executor executor,
+ OnViewAppliedListener listener, OnClickHandler handler) {
+ RemoteViews rvToApply = getRemoteViewsToApply(context);
+
+ // In the case that a view has this RemoteViews applied in one orientation, is persisted
+ // across orientation change, and has the RemoteViews re-applied in the new orientation,
+ // we throw an exception, since the layouts may be completely unrelated.
+ if (hasLandscapeAndPortraitLayouts()) {
+ if (v.getId() != rvToApply.getLayoutId()) {
+ throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
+ " that does not share the same root layout id.");
+ }
+ }
+
+ return startTaskOnExecutor(new AsyncApplyTask(rvToApply, (ViewGroup) v.getParent(),
+ context, listener, handler, v), executor);
+ }
+
private void performApply(View v, ViewGroup parent, OnClickHandler handler) {
if (mActions != null) {
handler = handler == null ? DEFAULT_ON_CLICK_HANDLER : handler;
@@ -3058,4 +3419,90 @@
return new RemoteViews[size];
}
};
+
+ /**
+ * A representation of the view hierarchy. Only views which have a valid ID are added
+ * and can be searched.
+ */
+ private static class ViewTree {
+ private final View mRoot;
+
+ private ArrayList<ViewTree> mChildren;
+
+ private ViewTree(View root) {
+ mRoot = root;
+ }
+
+ public void createTree() {
+ if (mChildren != null) {
+ return;
+ }
+
+ mChildren = new ArrayList<>();
+ if (mRoot instanceof ViewGroup && mRoot.isRootNamespace()) {
+ ViewGroup vg = (ViewGroup) mRoot;
+ int count = vg.getChildCount();
+ for (int i = 0; i < count; i++) {
+ addViewChild(vg.getChildAt(i));
+ }
+ }
+ }
+
+ public ViewTree findViewTreeById(int id) {
+ if (mRoot.getId() == id) {
+ return this;
+ }
+ if (mChildren == null) {
+ return null;
+ }
+ for (ViewTree tree : mChildren) {
+ ViewTree result = tree.findViewTreeById(id);
+ if (result != null) {
+ return result;
+ }
+ }
+ return null;
+ }
+
+ public View findViewById(int id) {
+ if (mChildren == null) {
+ return mRoot.findViewById(id);
+ }
+ ViewTree tree = findViewTreeById(id);
+ return tree == null ? null : tree.mRoot;
+ }
+
+ public void addChild(ViewTree child) {
+ if (mChildren == null) {
+ mChildren = new ArrayList<>();
+ }
+ child.createTree();
+ mChildren.add(child);
+ }
+
+ private void addViewChild(View v) {
+ final ViewTree target;
+
+ // If the view has a valid id, i.e., if can be found using findViewById, add it to the
+ // tree, otherwise skip this view and add its children instead.
+ if (v.getId() != 0) {
+ ViewTree tree = new ViewTree(v);
+ mChildren.add(tree);
+ target = tree;
+ } else {
+ target = this;
+ }
+
+ if (v instanceof ViewGroup && v.isRootNamespace()) {
+ if (target.mChildren == null) {
+ target.mChildren = new ArrayList<>();
+ ViewGroup vg = (ViewGroup) v;
+ int count = vg.getChildCount();
+ for (int i = 0; i < count; i++) {
+ target.addViewChild(vg.getChildAt(i));
+ }
+ }
+ }
+ }
+ }
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index c626af6..712a04b 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -115,7 +115,6 @@
import android.view.DragEvent;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
-import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -395,6 +394,14 @@
}
/**
+ * @return {@code true} if this object contains metadata that needs to
+ * be retained, {@code false} otherwise
+ */
+ public boolean hasMetadata() {
+ return mDrawablePadding != 0 || mHasTintMode || mHasTint;
+ }
+
+ /**
* Updates the list of displayed drawables to account for the current
* layout direction.
*
@@ -2174,7 +2181,7 @@
if (!drawables) {
// Clearing drawables... can we free the data structure?
if (dr != null) {
- if (dr.mDrawablePadding == 0) {
+ if (!dr.hasMetadata()) {
mDrawables = null;
} else {
// We need to retain the last set padding, so just clear
@@ -2377,7 +2384,7 @@
if (!drawables) {
// Clearing drawables... can we free the data structure?
if (dr != null) {
- if (dr.mDrawablePadding == 0) {
+ if (!dr.hasMetadata()) {
mDrawables = null;
} else {
// We need to retain the last set padding, so just clear
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index dbec740..015e60d 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -85,8 +85,10 @@
int callingUserId = getUserId();
if (canForward(newIntent, targetUserId)) {
- if (newIntent.getAction().equals(Intent.ACTION_CHOOSER)) {
+ if (Intent.ACTION_CHOOSER.equals(newIntent.getAction())) {
Intent innerIntent = (Intent) newIntent.getParcelableExtra(Intent.EXTRA_INTENT);
+ // At this point, innerIntent is not null. Otherwise, canForward would have returned
+ // false.
innerIntent.prepareToLeaveUser(callingUserId);
} else {
newIntent.prepareToLeaveUser(callingUserId);
@@ -124,7 +126,7 @@
Toast.makeText(this, getString(userMessageId), Toast.LENGTH_LONG).show();
}
} else {
- Slog.wtf(TAG, "the intent: " + newIntent + "cannot be forwarded from user "
+ Slog.wtf(TAG, "the intent: " + newIntent + " cannot be forwarded from user "
+ callingUserId + " to user " + targetUserId);
}
finish();
@@ -132,7 +134,7 @@
boolean canForward(Intent intent, int targetUserId) {
IPackageManager ipm = AppGlobals.getPackageManager();
- if (intent.getAction().equals(Intent.ACTION_CHOOSER)) {
+ if (Intent.ACTION_CHOOSER.equals(intent.getAction())) {
// The EXTRA_INITIAL_INTENTS may not be allowed to be forwarded.
if (intent.hasExtra(Intent.EXTRA_INITIAL_INTENTS)) {
Slog.wtf(TAG, "An chooser intent with extra initial intents cannot be forwarded to"
@@ -145,6 +147,11 @@
return false;
}
intent = (Intent) intent.getParcelableExtra(Intent.EXTRA_INTENT);
+ if (intent == null) {
+ Slog.wtf(TAG, "Cannot forward a chooser intent with no extra "
+ + Intent.EXTRA_INTENT);
+ return false;
+ }
}
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
if (intent.getSelector() != null) {
diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java
index 9a178837..956ee8c 100644
--- a/core/java/com/android/internal/app/LocalePickerWithRegion.java
+++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java
@@ -138,22 +138,12 @@
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
- Locale sortingLocale;
- if (mCountryMode) {
- if (mParentLocale == null) {
- sortingLocale = Locale.getDefault();
- this.getActivity().setTitle(R.string.country_selection_title);
- } else {
- sortingLocale = mParentLocale.getLocale();
- this.getActivity().setTitle(mParentLocale.getFullNameNative());
- }
- } else {
- sortingLocale = Locale.getDefault();
- this.getActivity().setTitle(R.string.language_selection_title);
- }
+ final Locale sortingLocale = (mCountryMode && mParentLocale != null)
+ ? mParentLocale.getLocale()
+ : Locale.getDefault();
mAdapter = new SuggestedLocaleAdapter(mLocaleList, mCountryMode);
- LocaleHelper.LocaleInfoComparator comp =
+ final LocaleHelper.LocaleInfoComparator comp =
new LocaleHelper.LocaleInfoComparator(sortingLocale);
mAdapter.sort(comp);
setListAdapter(mAdapter);
@@ -173,6 +163,17 @@
@Override
public void onResume() {
super.onResume();
+
+ if (mCountryMode) {
+ if (mParentLocale == null) {
+ this.getActivity().setTitle(R.string.country_selection_title);
+ } else {
+ this.getActivity().setTitle(mParentLocale.getFullNameNative());
+ }
+ } else {
+ this.getActivity().setTitle(R.string.language_selection_title);
+ }
+
getListView().requestFocus();
}
diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java
index 02efcc6..ce03bb8 100644
--- a/core/java/com/android/internal/widget/FloatingToolbar.java
+++ b/core/java/com/android/internal/widget/FloatingToolbar.java
@@ -322,7 +322,7 @@
/* View components */
private final ViewGroup mContentContainer; // holds all contents.
private final ViewGroup mMainPanel; // holds menu items that are initially displayed.
- private final ListView mOverflowPanel; // holds menu items hidden in the overflow.
+ private final OverflowPanel mOverflowPanel; // holds menu items hidden in the overflow.
private final ImageButton mOverflowButton; // opens/closes the overflow.
/* overflow button drawables. */
private final Drawable mArrow;
@@ -895,6 +895,7 @@
private void setPanelsStatesAtRestingPosition() {
mOverflowButton.setEnabled(true);
+ mOverflowPanel.awakenScrollBars();
if (mIsOverflowOpen) {
// Set open state.
@@ -1333,27 +1334,8 @@
return overflowButton;
}
- private ListView createOverflowPanel() {
- final ListView overflowPanel = new ListView(FloatingToolbarPopup.this.mContext) {
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- // Update heightMeasureSpec to make sure that this view is not clipped
- // as we offset it's coordinates with respect to it's parent.
- heightMeasureSpec = MeasureSpec.makeMeasureSpec(
- mOverflowPanelSize.getHeight() - mOverflowButtonSize.getHeight(),
- MeasureSpec.EXACTLY);
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
-
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- if (isOverflowAnimating()) {
- // Eat the touch event.
- return true;
- }
- return super.dispatchTouchEvent(ev);
- }
- };
+ private OverflowPanel createOverflowPanel() {
+ final OverflowPanel overflowPanel = new OverflowPanel(this);
overflowPanel.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
overflowPanel.setDivider(null);
@@ -1464,6 +1446,43 @@
}
/**
+ * A custom ListView for the overflow panel.
+ */
+ private static final class OverflowPanel extends ListView {
+
+ private final FloatingToolbarPopup mPopup;
+
+ OverflowPanel(FloatingToolbarPopup popup) {
+ super(Preconditions.checkNotNull(popup).mContext);
+ this.mPopup = popup;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // Update heightMeasureSpec to make sure that this view is not clipped
+ // as we offset it's coordinates with respect to it's parent.
+ int height = mPopup.mOverflowPanelSize.getHeight()
+ - mPopup.mOverflowButtonSize.getHeight();
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ if (mPopup.isOverflowAnimating()) {
+ // Eat the touch event.
+ return true;
+ }
+ return super.dispatchTouchEvent(ev);
+ }
+
+ @Override
+ protected boolean awakenScrollBars() {
+ return super.awakenScrollBars();
+ }
+ }
+
+ /**
* A custom interpolator used for various floating toolbar animations.
*/
private static final class LogAccelerateInterpolator implements Interpolator {
diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp
index b1d4e26..a9003c1 100644
--- a/core/jni/android_view_RenderNode.cpp
+++ b/core/jni/android_view_RenderNode.cpp
@@ -15,6 +15,7 @@
*/
#define LOG_TAG "OpenGLRenderer"
+#define ATRACE_TAG ATRACE_TAG_VIEW
#include <EGL/egl.h>
@@ -24,7 +25,10 @@
#include <android_runtime/AndroidRuntime.h>
#include <Animator.h>
+#include <DamageAccumulator.h>
+#include <Matrix.h>
#include <RenderNode.h>
+#include <TreeInfo.h>
#include <Paint.h>
#include "core_jni_helpers.h"
@@ -462,6 +466,69 @@
}
// ----------------------------------------------------------------------------
+// SurfaceView position callback
+// ----------------------------------------------------------------------------
+
+jmethodID gSurfaceViewPositionUpdateMethod;
+
+static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
+ jlong renderNodePtr, jobject surfaceview) {
+ class SurfaceViewPositionUpdater : public RenderNode::PositionListener {
+ public:
+ SurfaceViewPositionUpdater(JNIEnv* env, jobject surfaceview) {
+ env->GetJavaVM(&mVm);
+ mWeakRef = env->NewWeakGlobalRef(surfaceview);
+ }
+
+ virtual ~SurfaceViewPositionUpdater() {
+ jnienv()->DeleteWeakGlobalRef(mWeakRef);
+ mWeakRef = nullptr;
+ }
+
+ virtual void onPositionUpdated(RenderNode& node, const TreeInfo& info) override {
+ if (CC_UNLIKELY(!mWeakRef || !info.updateWindowPositions)) return;
+ ATRACE_NAME("Update SurfaceView position");
+
+ JNIEnv* env = jnienv();
+ jobject localref = env->NewLocalRef(mWeakRef);
+ if (CC_UNLIKELY(!localref)) {
+ jnienv()->DeleteWeakGlobalRef(mWeakRef);
+ mWeakRef = nullptr;
+ return;
+ }
+ Matrix4 transform;
+ info.damageAccumulator->computeCurrentTransform(&transform);
+ const RenderProperties& props = node.properties();
+ uirenderer::Rect bounds(props.getWidth(), props.getHeight());
+ transform.mapRect(bounds);
+ bounds.left -= info.windowInsetLeft;
+ bounds.right -= info.windowInsetLeft;
+ bounds.top -= info.windowInsetTop;
+ bounds.bottom -= info.windowInsetTop;
+ env->CallVoidMethod(localref, gSurfaceViewPositionUpdateMethod,
+ (jlong) info.frameNumber, (jint) bounds.left, (jint) bounds.top,
+ (jint) bounds.right, (jint) bounds.bottom);
+ env->DeleteLocalRef(localref);
+ }
+
+ private:
+ JNIEnv* jnienv() {
+ JNIEnv* env;
+ if (mVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", mVm);
+ }
+ return env;
+ }
+
+ JavaVM* mVm;
+ jobject mWeakRef;
+ };
+
+ RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+ renderNode->setPositionListener(new SurfaceViewPositionUpdater(env, surfaceview));
+}
+
+// ----------------------------------------------------------------------------
// JNI Glue
// ----------------------------------------------------------------------------
@@ -539,9 +606,14 @@
{ "nAddAnimator", "(JJ)V", (void*) android_view_RenderNode_addAnimator },
{ "nEndAllAnimators", "(J)V", (void*) android_view_RenderNode_endAllAnimators },
+
+ { "nRequestPositionUpdates", "(JLandroid/view/SurfaceView;)V", (void*) android_view_RenderNode_requestPositionUpdates },
};
int register_android_view_RenderNode(JNIEnv* env) {
+ jclass clazz = FindClassOrDie(env, "android/view/SurfaceView");
+ gSurfaceViewPositionUpdateMethod = GetMethodIDOrDie(env, clazz,
+ "updateWindowPositionRT", "(JIIII)V");
return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
}
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 8c907dd..acd0501 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -134,7 +134,14 @@
virtual void prepareTree(TreeInfo& info) {
info.errorHandler = this;
+ // TODO: This is hacky
+ info.windowInsetLeft = -stagingProperties().getLeft();
+ info.windowInsetTop = -stagingProperties().getTop();
+ info.updateWindowPositions = true;
RenderNode::prepareTree(info);
+ info.updateWindowPositions = false;
+ info.windowInsetLeft = 0;
+ info.windowInsetTop = 0;
info.errorHandler = NULL;
}
@@ -369,28 +376,28 @@
static void android_view_ThreadedRenderer_initialize(JNIEnv* env, jobject clazz,
jlong proxyPtr, jobject jsurface) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
- sp<ANativeWindow> window = android_view_Surface_getNativeWindow(env, jsurface);
- proxy->initialize(window);
+ sp<Surface> surface = android_view_Surface_getSurface(env, jsurface);
+ proxy->initialize(surface);
}
static void android_view_ThreadedRenderer_updateSurface(JNIEnv* env, jobject clazz,
jlong proxyPtr, jobject jsurface) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
- sp<ANativeWindow> window;
+ sp<Surface> surface;
if (jsurface) {
- window = android_view_Surface_getNativeWindow(env, jsurface);
+ surface = android_view_Surface_getSurface(env, jsurface);
}
- proxy->updateSurface(window);
+ proxy->updateSurface(surface);
}
static jboolean android_view_ThreadedRenderer_pauseSurface(JNIEnv* env, jobject clazz,
jlong proxyPtr, jobject jsurface) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
- sp<ANativeWindow> window;
+ sp<Surface> surface;
if (jsurface) {
- window = android_view_Surface_getNativeWindow(env, jsurface);
+ surface = android_view_Surface_getSurface(env, jsurface);
}
- return proxy->pauseSurface(window);
+ return proxy->pauseSurface(surface);
}
static void android_view_ThreadedRenderer_setup(JNIEnv* env, jobject clazz, jlong proxyPtr,
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7729d48c..cc48902 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -292,9 +292,12 @@
<protected-broadcast android:name="android.intent.action.REMOTE_BUGREPORT_FINISHED" />
<protected-broadcast android:name="android.intent.action.REMOTE_BUGREPORT_DISPATCH" />
+ <!-- Legacy -->
<protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_START" />
<protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_END" />
+ <protected-broadcast android:name="com.android.server.task.controllers.IdleController.ACTION_TRIGGER_IDLE" />
+
<protected-broadcast android:name="android.intent.action.HDMI_PLUGGED" />
<protected-broadcast android:name="android.intent.action.PHONE_STATE" />
@@ -2874,6 +2877,12 @@
<permission android:name="android.permission.MANAGE_SOUND_TRIGGER"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi Allows trusted applications to dispatch managed provisioning message to Managed
+ Provisioning app. If requesting app does not have permission, it will be ignored.
+ @hide -->
+ <permission android:name="android.permission.DISPATCH_PROVISIONING_MESSAGE"
+ android:protectionLevel="signature|privileged" />
+
<application android:process="system"
android:persistent="true"
android:hasCode="false"
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index b45f8bb..6669bae 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -87,7 +87,6 @@
android:layout_marginStart="2dp"
android:layout_marginEnd="2dp"
android:visibility="gone"
- android:maxWidth="72dp"
android:singleLine="true"/>
<TextView
android:id="@+id/time_divider"
diff --git a/core/res/res/menu/language_selection_list.xml b/core/res/res/menu/language_selection_list.xml
index 63b9627..377c4fa 100644
--- a/core/res/res/menu/language_selection_list.xml
+++ b/core/res/res/menu/language_selection_list.xml
@@ -14,12 +14,12 @@
limitations under the License.
-->
-<menu xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto">
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/locale_search_menu"
android:title="@string/locale_search_menu"
- app:showAsAction="always"
- app:actionViewClass="android.widget.SearchView" />
+ android:icon="@*android:drawable/ic_search_api_material"
+ android:showAsAction="always|collapseActionView"
+ android:actionViewClass="android.widget.SearchView" />
</menu>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 329e2e5..5c5aff0 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2697,6 +2697,9 @@
<public type="attr" name="endY" />
<public type="attr" name="offset" />
<public type="attr" name="abiOverride" />
+ <public type="attr" name="bitmap" />
+ <public type="attr" name="hotSpotX" />
+ <public type="attr" name="hotSpotY" />
<public type="style" name="Theme.Material.Light.DialogWhenLarge.DarkActionBar" />
<public type="style" name="Widget.Material.SeekBar.Discrete" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e5a6226..4e10d39 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4176,7 +4176,7 @@
<string name="language_selection_title">Language preference</string>
<!-- Title for the region selection screen [CHAR LIMIT=25] -->
<string name="country_selection_title">Region preference</string>
- <!-- Hint text in a search edit box (used to filter long language / country lists) [CHAR LIMIT=20] -->
+ <!-- Hint text in a search edit box (used to filter long language / country lists) [CHAR LIMIT=25] -->
<string name="search_language_hint">Type language name</string>
<!-- List section subheader for the language picker, containing a list of suggested languages determined by the default region [CHAR LIMIT=30] -->
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 6988b02..c232bd1 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -238,6 +238,7 @@
LOCAL_SRC_FILES += \
$(hwui_test_common_src_files) \
+ tests/unit/BufferPoolTests.cpp \
tests/unit/CanvasStateTests.cpp \
tests/unit/ClipAreaTests.cpp \
tests/unit/CrashHandlerInjector.cpp \
@@ -247,11 +248,11 @@
tests/unit/GpuMemoryTrackerTests.cpp \
tests/unit/LayerUpdateQueueTests.cpp \
tests/unit/LinearAllocatorTests.cpp \
- tests/unit/VectorDrawableTests.cpp \
tests/unit/OffscreenBufferPoolTests.cpp \
+ tests/unit/SkiaBehaviorTests.cpp \
tests/unit/StringUtilsTests.cpp \
- tests/unit/BufferPoolTests.cpp \
- tests/unit/TextDropShadowCacheTests.cpp
+ tests/unit/TextDropShadowCacheTests.cpp \
+ tests/unit/VectorDrawableTests.cpp
ifeq (true, $(HWUI_NEW_OPS))
LOCAL_SRC_FILES += \
diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp
index 2184755..e3a5f3e 100644
--- a/libs/hwui/BakedOpDispatcher.cpp
+++ b/libs/hwui/BakedOpDispatcher.cpp
@@ -663,7 +663,7 @@
}
void BakedOpDispatcher::onShadowOp(BakedOpRenderer& renderer, const ShadowOp& op, const BakedOpState& state) {
- TessellationCache::vertexBuffer_pair_t buffers = *(op.shadowTask->getResult());
+ TessellationCache::vertexBuffer_pair_t buffers = op.shadowTask->getResult();
renderShadow(renderer, state, op.casterAlpha, buffers.first, buffers.second);
}
diff --git a/libs/hwui/ClipArea.cpp b/libs/hwui/ClipArea.cpp
index 160090d..9c08b4d 100644
--- a/libs/hwui/ClipArea.cpp
+++ b/libs/hwui/ClipArea.cpp
@@ -213,6 +213,7 @@
void ClipArea::clipRectWithTransform(const Rect& r, const mat4* transform,
SkRegion::Op op) {
+ if (!mPostViewportClipObserved && op == SkRegion::kIntersect_Op) op = SkRegion::kReplace_Op;
onClipUpdated();
switch (mMode) {
case ClipMode::Rectangle:
@@ -228,6 +229,7 @@
}
void ClipArea::clipRegion(const SkRegion& region, SkRegion::Op op) {
+ if (!mPostViewportClipObserved && op == SkRegion::kIntersect_Op) op = SkRegion::kReplace_Op;
onClipUpdated();
enterRegionMode();
mClipRegion.op(region, op);
@@ -236,6 +238,7 @@
void ClipArea::clipPathWithTransform(const SkPath& path, const mat4* transform,
SkRegion::Op op) {
+ if (!mPostViewportClipObserved && op == SkRegion::kIntersect_Op) op = SkRegion::kReplace_Op;
onClipUpdated();
SkMatrix skTransform;
transform->copyTo(skTransform);
diff --git a/libs/hwui/DamageAccumulator.h b/libs/hwui/DamageAccumulator.h
index e44fc20..250296e 100644
--- a/libs/hwui/DamageAccumulator.h
+++ b/libs/hwui/DamageAccumulator.h
@@ -57,7 +57,7 @@
// Returns the current dirty area, *NOT* transformed by pushed transforms
void peekAtDirty(SkRect* dest) const;
- void computeCurrentTransform(Matrix4* outMatrix) const;
+ ANDROID_API void computeCurrentTransform(Matrix4* outMatrix) const;
void finish(SkRect* totalDirty);
diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp
index 57e5b9d..185acce 100644
--- a/libs/hwui/FrameBuilder.cpp
+++ b/libs/hwui/FrameBuilder.cpp
@@ -203,7 +203,9 @@
mCanvasState.setClippingOutline(mAllocator, &(properties.getOutline()));
}
- if (!mCanvasState.quickRejectConservative(0, 0, width, height)) {
+ bool quickRejected = properties.getClipToBounds()
+ && mCanvasState.quickRejectConservative(0, 0, width, height);
+ if (!quickRejected) {
// not rejected, so defer render as either Layer, or direct (possibly wrapped in saveLayer)
if (node.getLayer()) {
// HW layer
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index d4588ed..bade216 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -381,6 +381,10 @@
bool childFunctorsNeedLayer = mProperties.prepareForFunctorPresence(
willHaveFunctor, functorsNeedLayer);
+ if (CC_UNLIKELY(mPositionListener.get())) {
+ mPositionListener->onPositionUpdated(*this, info);
+ }
+
prepareLayer(info, animatorDirtyMask);
if (info.mode == TreeInfo::MODE_FULL) {
pushStagingDisplayListChanges(info);
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index 8e4a3df..f248de54 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -209,6 +209,19 @@
OffscreenBuffer** getLayerHandle() { return &mLayer; } // ugh...
#endif
+ class ANDROID_API PositionListener {
+ public:
+ virtual ~PositionListener() {}
+ virtual void onPositionUpdated(RenderNode& node, const TreeInfo& info) = 0;
+ };
+
+ // Note this is not thread safe, this needs to be called
+ // before the RenderNode is used for drawing.
+ // RenderNode takes ownership of the pointer
+ ANDROID_API void setPositionListener(PositionListener* listener) {
+ mPositionListener.reset(listener);
+ }
+
private:
typedef key_value_pair_t<float, DrawRenderNodeOp*> ZDrawRenderNodeOpPair;
@@ -317,6 +330,8 @@
// This is *NOT* thread-safe, and should therefore only be tracking
// mDisplayList, not mStagingDisplayList.
uint32_t mParentCount;
+
+ std::unique_ptr<PositionListener> mPositionListener;
}; // class RenderNode
} /* namespace uirenderer */
diff --git a/libs/hwui/TessellationCache.cpp b/libs/hwui/TessellationCache.cpp
index 461e819..fd9fb852 100644
--- a/libs/hwui/TessellationCache.cpp
+++ b/libs/hwui/TessellationCache.cpp
@@ -242,23 +242,21 @@
spotBuffer);
}
-class ShadowProcessor : public TaskProcessor<TessellationCache::vertexBuffer_pair_t*> {
+class ShadowProcessor : public TaskProcessor<TessellationCache::vertexBuffer_pair_t> {
public:
ShadowProcessor(Caches& caches)
- : TaskProcessor<TessellationCache::vertexBuffer_pair_t*>(&caches.tasks) {}
+ : TaskProcessor<TessellationCache::vertexBuffer_pair_t>(&caches.tasks) {}
~ShadowProcessor() {}
- virtual void onProcess(const sp<Task<TessellationCache::vertexBuffer_pair_t*> >& task) override {
+ virtual void onProcess(const sp<Task<TessellationCache::vertexBuffer_pair_t> >& task) override {
TessellationCache::ShadowTask* t = static_cast<TessellationCache::ShadowTask*>(task.get());
ATRACE_NAME("shadow tessellation");
- VertexBuffer* ambientBuffer = new VertexBuffer;
- VertexBuffer* spotBuffer = new VertexBuffer;
tessellateShadows(&t->drawTransform, &t->localClip, t->opaque, &t->casterPerimeter,
&t->transformXY, &t->transformZ, t->lightCenter, t->lightRadius,
- *ambientBuffer, *spotBuffer);
+ t->ambientBuffer, t->spotBuffer);
- t->setResult(new TessellationCache::vertexBuffer_pair_t(ambientBuffer, spotBuffer));
+ t->setResult(TessellationCache::vertexBuffer_pair_t(&t->ambientBuffer, &t->spotBuffer));
}
};
@@ -373,7 +371,7 @@
task = static_cast<ShadowTask*>(mShadowCache.get(key));
}
LOG_ALWAYS_FATAL_IF(task == nullptr, "shadow not precached");
- outBuffers = *(task->getResult());
+ outBuffers = task->getResult();
}
sp<TessellationCache::ShadowTask> TessellationCache::getShadowTask(
@@ -392,13 +390,6 @@
return task;
}
-TessellationCache::ShadowTask::~ShadowTask() {
- TessellationCache::vertexBuffer_pair_t* bufferPair = getResult();
- delete bufferPair->getFirst();
- delete bufferPair->getSecond();
- delete bufferPair;
-}
-
///////////////////////////////////////////////////////////////////////////////
// Tessellation precaching
///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/TessellationCache.h b/libs/hwui/TessellationCache.h
index 977c2d9e..6dcc8120 100644
--- a/libs/hwui/TessellationCache.h
+++ b/libs/hwui/TessellationCache.h
@@ -21,6 +21,7 @@
#include "Matrix.h"
#include "Rect.h"
#include "Vector.h"
+#include "VertexBuffer.h"
#include "thread/TaskProcessor.h"
#include "utils/Macros.h"
#include "utils/Pair.h"
@@ -89,7 +90,7 @@
hash_t hash() const;
};
- class ShadowTask : public Task<TessellationCache::vertexBuffer_pair_t*> {
+ class ShadowTask : public Task<vertexBuffer_pair_t> {
public:
ShadowTask(const Matrix4* drawTransform, const Rect& localClip, bool opaque,
const SkPath* casterPerimeter, const Matrix4* transformXY, const Matrix4* transformZ,
@@ -104,13 +105,11 @@
, lightRadius(lightRadius) {
}
- ~ShadowTask();
-
/* Note - we deep copy all task parameters, because *even though* pointers into Allocator
* controlled objects (like the SkPath and Matrix4s) should be safe for the entire frame,
* certain Allocators are destroyed before trim() is called to flush incomplete tasks.
*
- * These deep copies could be avoided, long term, by cancelling or flushing outstanding
+ * These deep copies could be avoided, long term, by canceling or flushing outstanding
* tasks before tearing down single-frame LinearAllocators.
*/
const Matrix4 drawTransform;
@@ -121,6 +120,8 @@
const Matrix4 transformZ;
const Vector3 lightCenter;
const float lightRadius;
+ VertexBuffer ambientBuffer;
+ VertexBuffer spotBuffer;
};
TessellationCache();
@@ -217,12 +218,12 @@
///////////////////////////////////////////////////////////////////////////////
// Shadow tessellation caching
///////////////////////////////////////////////////////////////////////////////
- sp<TaskProcessor<vertexBuffer_pair_t*> > mShadowProcessor;
+ sp<TaskProcessor<vertexBuffer_pair_t> > mShadowProcessor;
// holds a pointer, and implicit strong ref to each shadow task of the frame
- LruCache<ShadowDescription, Task<vertexBuffer_pair_t*>*> mShadowCache;
- class BufferPairRemovedListener : public OnEntryRemoved<ShadowDescription, Task<vertexBuffer_pair_t*>*> {
- void operator()(ShadowDescription& description, Task<vertexBuffer_pair_t*>*& bufferPairTask) override {
+ LruCache<ShadowDescription, Task<vertexBuffer_pair_t>*> mShadowCache;
+ class BufferPairRemovedListener : public OnEntryRemoved<ShadowDescription, Task<vertexBuffer_pair_t>*> {
+ void operator()(ShadowDescription& description, Task<vertexBuffer_pair_t>*& bufferPairTask) override {
bufferPairTask->decStrong(nullptr);
}
};
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
index be25516..accd303 100644
--- a/libs/hwui/TreeInfo.h
+++ b/libs/hwui/TreeInfo.h
@@ -86,6 +86,12 @@
#endif
ErrorHandler* errorHandler = nullptr;
+ // Frame number for use with synchronized surfaceview position updating
+ int64_t frameNumber = -1;
+ int32_t windowInsetLeft = 0;
+ int32_t windowInsetTop = 0;
+ bool updateWindowPositions = false;
+
struct Out {
bool hasFunctors = false;
// This is only updated if evaluateAnimations is true
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index d411621..ea702c0 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -92,18 +92,18 @@
}
}
-void CanvasContext::setSurface(ANativeWindow* window) {
+void CanvasContext::setSurface(Surface* surface) {
ATRACE_CALL();
- mNativeWindow = window;
+ mNativeSurface = surface;
if (mEglSurface != EGL_NO_SURFACE) {
mEglManager.destroySurface(mEglSurface);
mEglSurface = EGL_NO_SURFACE;
}
- if (window) {
- mEglSurface = mEglManager.createSurface(window);
+ if (surface) {
+ mEglSurface = mEglManager.createSurface(surface);
}
if (mEglSurface != EGL_NO_SURFACE) {
@@ -127,8 +127,8 @@
mSwapBehavior = swapBehavior;
}
-void CanvasContext::initialize(ANativeWindow* window) {
- setSurface(window);
+void CanvasContext::initialize(Surface* surface) {
+ setSurface(surface);
#if !HWUI_NEW_OPS
if (mCanvas) return;
mCanvas = new OpenGLRenderer(mRenderThread.renderState());
@@ -136,11 +136,11 @@
#endif
}
-void CanvasContext::updateSurface(ANativeWindow* window) {
- setSurface(window);
+void CanvasContext::updateSurface(Surface* surface) {
+ setSurface(surface);
}
-bool CanvasContext::pauseSurface(ANativeWindow* window) {
+bool CanvasContext::pauseSurface(Surface* surface) {
return mRenderThread.removeFrameCallback(this);
}
@@ -204,6 +204,10 @@
info.renderer = mCanvas;
#endif
+ if (CC_LIKELY(mNativeSurface.get())) {
+ info.frameNumber = static_cast<int64_t>(mNativeSurface->getNextFrameNumber());
+ }
+
mAnimationContext->startFrame(info.mode);
for (const sp<RenderNode>& node : mRenderNodes) {
// Only the primary target node will be drawn full - all other nodes would get drawn in
@@ -219,7 +223,7 @@
freePrefetechedLayers();
GL_CHECKPOINT(MODERATE);
- if (CC_UNLIKELY(!mNativeWindow.get())) {
+ if (CC_UNLIKELY(!mNativeSurface.get())) {
mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
info.out.canDrawThisFrame = false;
return;
@@ -242,8 +246,9 @@
} else {
// We're maybe behind? Find out for sure
int runningBehind = 0;
- mNativeWindow->query(mNativeWindow.get(),
- NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &runningBehind);
+ // TODO: Have this method be on Surface, too, not just ANativeWindow...
+ ANativeWindow* window = mNativeSurface.get();
+ window->query(window, NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &runningBehind);
info.out.canDrawThisFrame = !runningBehind;
}
} else {
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 63a7977..168166e 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -39,6 +39,7 @@
#include <SkBitmap.h>
#include <SkRect.h>
#include <utils/Functor.h>
+#include <gui/Surface.h>
#include <set>
#include <string>
@@ -75,10 +76,10 @@
// Won't take effect until next EGLSurface creation
void setSwapBehavior(SwapBehavior swapBehavior);
- void initialize(ANativeWindow* window);
- void updateSurface(ANativeWindow* window);
- bool pauseSurface(ANativeWindow* window);
- bool hasSurface() { return mNativeWindow.get(); }
+ void initialize(Surface* surface);
+ void updateSurface(Surface* surface);
+ bool pauseSurface(Surface* surface);
+ bool hasSurface() { return mNativeSurface.get(); }
void setup(int width, int height, float lightRadius,
uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
@@ -172,7 +173,7 @@
// lifecycle tracking
friend class android::uirenderer::RenderState;
- void setSurface(ANativeWindow* window);
+ void setSurface(Surface* window);
void requireSurface();
void freePrefetechedLayers();
@@ -182,7 +183,7 @@
RenderThread& mRenderThread;
EglManager& mEglManager;
- sp<ANativeWindow> mNativeWindow;
+ sp<Surface> mNativeSurface;
EGLSurface mEglSurface = EGL_NO_SURFACE;
bool mBufferPreserved = false;
SwapBehavior mSwapBehavior = kSwap_default;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 1d1b144..7c6cd7e 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -139,38 +139,38 @@
postAndWait(task); // block since name/value pointers owned by caller
}
-CREATE_BRIDGE2(initialize, CanvasContext* context, ANativeWindow* window) {
- args->context->initialize(args->window);
+CREATE_BRIDGE2(initialize, CanvasContext* context, Surface* surface) {
+ args->context->initialize(args->surface);
return nullptr;
}
-void RenderProxy::initialize(const sp<ANativeWindow>& window) {
+void RenderProxy::initialize(const sp<Surface>& surface) {
SETUP_TASK(initialize);
args->context = mContext;
- args->window = window.get();
+ args->surface = surface.get();
post(task);
}
-CREATE_BRIDGE2(updateSurface, CanvasContext* context, ANativeWindow* window) {
- args->context->updateSurface(args->window);
+CREATE_BRIDGE2(updateSurface, CanvasContext* context, Surface* surface) {
+ args->context->updateSurface(args->surface);
return nullptr;
}
-void RenderProxy::updateSurface(const sp<ANativeWindow>& window) {
+void RenderProxy::updateSurface(const sp<Surface>& surface) {
SETUP_TASK(updateSurface);
args->context = mContext;
- args->window = window.get();
+ args->surface = surface.get();
postAndWait(task);
}
-CREATE_BRIDGE2(pauseSurface, CanvasContext* context, ANativeWindow* window) {
- return (void*) args->context->pauseSurface(args->window);
+CREATE_BRIDGE2(pauseSurface, CanvasContext* context, Surface* surface) {
+ return (void*) args->context->pauseSurface(args->surface);
}
-bool RenderProxy::pauseSurface(const sp<ANativeWindow>& window) {
+bool RenderProxy::pauseSurface(const sp<Surface>& surface) {
SETUP_TASK(pauseSurface);
args->context = mContext;
- args->window = window.get();
+ args->surface = surface.get();
return (bool) postAndWait(task);
}
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 4180d802..178724a 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -67,9 +67,9 @@
ANDROID_API bool loadSystemProperties();
ANDROID_API void setName(const char* name);
- ANDROID_API void initialize(const sp<ANativeWindow>& window);
- ANDROID_API void updateSurface(const sp<ANativeWindow>& window);
- ANDROID_API bool pauseSurface(const sp<ANativeWindow>& window);
+ ANDROID_API void initialize(const sp<Surface>& surface);
+ ANDROID_API void updateSurface(const sp<Surface>& surface);
+ ANDROID_API bool pauseSurface(const sp<Surface>& surface);
ANDROID_API void setup(int width, int height, float lightRadius,
uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
ANDROID_API void setLightCenter(const Vector3& lightCenter);
diff --git a/libs/hwui/tests/macrobench/TestSceneRunner.cpp b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
index a843e92..58c0876 100644
--- a/libs/hwui/tests/macrobench/TestSceneRunner.cpp
+++ b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
@@ -112,9 +112,9 @@
scene->doFrame(i);
proxy->syncAndDrawFrame();
}
- proxy->fence();
- nsecs_t done = systemTime(CLOCK_MONOTONIC);
if (opts.reportFrametimeWeight) {
+ proxy->fence();
+ nsecs_t done = systemTime(CLOCK_MONOTONIC);
avgMs.add((done - vsync) / 1000000.0);
if (i % 10 == 9) {
printf("Average frametime %.3fms\n", avgMs.average());
diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
index 01bfc5a..20d2f1f 100644
--- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp
+++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
@@ -455,6 +455,23 @@
}
}
+TEST(RecordingCanvas, firstClipWillReplace) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ canvas.save(SaveFlags::MatrixClip);
+ // since no explicit clip set on canvas, this should be the one observed on op:
+ canvas.clipRect(-100, -100, 300, 300, SkRegion::kIntersect_Op);
+
+ SkPaint paint;
+ paint.setColor(SK_ColorWHITE);
+ canvas.drawRect(0, 0, 100, 100, paint);
+
+ canvas.restore();
+ });
+ ASSERT_EQ(1u, dl->getOps().size()) << "Must have one op";
+ // first clip must be preserved, even if it extends beyond canvas bounds
+ EXPECT_CLIP_RECT(Rect(-100, -100, 300, 300), dl->getOps()[0]->localClip);
+}
+
TEST(RecordingCanvas, insertReorderBarrier) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
canvas.drawRect(0, 0, 400, 400, SkPaint());
diff --git a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
new file mode 100644
index 0000000..586625b
--- /dev/null
+++ b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
@@ -0,0 +1,43 @@
+/*
+ * 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 "tests/common/TestUtils.h"
+
+#include <gtest/gtest.h>
+#include <SkShader.h>
+
+using namespace android;
+using namespace android::uirenderer;
+
+/**
+ * 1x1 bitmaps must not be optimized into solid color shaders, since HWUI can't
+ * compose/render color shaders
+ */
+TEST(SkiaBehavior, CreateBitmapShader1x1) {
+ SkBitmap origBitmap = TestUtils::createSkBitmap(1, 1);
+ std::unique_ptr<SkShader> s(SkShader::CreateBitmapShader(
+ origBitmap,
+ SkShader::kClamp_TileMode,
+ SkShader::kRepeat_TileMode));
+
+ SkBitmap bitmap;
+ SkShader::TileMode xy[2];
+ ASSERT_TRUE(s->isABitmap(&bitmap, nullptr, xy))
+ << "1x1 bitmap shader must query as bitmap shader";
+ EXPECT_EQ(SkShader::kClamp_TileMode, xy[0]);
+ EXPECT_EQ(SkShader::kRepeat_TileMode, xy[1]);
+ EXPECT_EQ(origBitmap.pixelRef(), bitmap.pixelRef());
+}
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index 20491e4..f9a23f9 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -32,6 +32,7 @@
import android.graphics.drawable.Icon;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.net.Uri;
+import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
@@ -114,6 +115,7 @@
private final int mTunerCount;
private final boolean mCanRecord;
private final boolean mIsHardwareInput;
+ private final Bundle mExtras;
// Attributes from XML meta data.
private String mSetupActivity;
@@ -252,7 +254,7 @@
*/
private TvInputInfo(ResolveInfo service, String id, String parentId, int type,
boolean isHardwareInput, boolean isConnectedToHdmiSwitch, int tunerCount,
- boolean canRecord) {
+ boolean canRecord, Bundle extras) {
mService = service;
mId = id;
mParentId = parentId;
@@ -261,6 +263,7 @@
mIsConnectedToHdmiSwitch = isConnectedToHdmiSwitch;
mTunerCount = tunerCount;
mCanRecord = canRecord;
+ mExtras = extras;
}
/**
@@ -362,6 +365,14 @@
}
/**
+ * Returns the extras associated with this TV input.
+ * @hide
+ */
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ /**
* Returns the HDMI device information of this TV input.
* @hide
*/
@@ -524,6 +535,7 @@
dest.writeInt(mLabelResId);
dest.writeString(mLabel);
dest.writeByte(mIsConnectedToHdmiSwitch ? (byte) 1 : 0);
+ dest.writeBundle(mExtras);
}
private Drawable loadServiceIcon(Context context) {
@@ -563,6 +575,7 @@
mLabelResId = in.readInt();
mLabel = in.readString();
mIsConnectedToHdmiSwitch = in.readByte() == 1;
+ mExtras = in.readBundle();
}
/**
@@ -604,6 +617,7 @@
private HdmiDeviceInfo mHdmiDeviceInfo;
private String mParentId;
private TvInputHardwareInfo mTvInputHardwareInfo;
+ private Bundle mExtras;
/**
* Constructs a new builder for {@link TvInputInfo}.
@@ -732,6 +746,18 @@
}
/**
+ * Sets the extras associated with this TV input.
+ *
+ * @param extras The extras associated with this TV input.
+ * @return This Builder object to allow for chaining of calls to builder methods.
+ * @hide
+ */
+ public Builder setExtras(Bundle extras) {
+ this.mExtras = extras;
+ return this;
+ }
+
+ /**
* Creates a {@link TvInputInfo} instance with the specified fields. Most of the information
* is obtained by parsing the AndroidManifest and {@link TvInputService#SERVICE_META_DATA}
* for the {@link TvInputService} this TV input implements.
@@ -765,7 +791,7 @@
}
TvInputInfo info = new TvInputInfo(mResolveInfo, id, mParentId, type, isHardwareInput,
- isConnectedToHdmiSwitch, mTunerCount, mCanRecord);
+ isConnectedToHdmiSwitch, mTunerCount, mCanRecord, mExtras);
return parseServiceMetadata(type, info);
}
diff --git a/packages/DocumentsUI/res/layout/drawer_layout.xml b/packages/DocumentsUI/res/layout/drawer_layout.xml
index 0146f14..e3def05 100644
--- a/packages/DocumentsUI/res/layout/drawer_layout.xml
+++ b/packages/DocumentsUI/res/layout/drawer_layout.xml
@@ -32,7 +32,7 @@
android:layout_height="match_parent"
android:orientation="vertical">
- <com.android.documentsui.DocumentsToolBar
+ <com.android.documentsui.DocumentsToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
@@ -48,7 +48,7 @@
android:layout_marginStart="4dp"
android:overlapAnchor="true" />
- </com.android.documentsui.DocumentsToolBar>
+ </com.android.documentsui.DocumentsToolbar>
<include layout="@layout/directory_cluster"/>
diff --git a/packages/DocumentsUI/res/layout/fixed_layout.xml b/packages/DocumentsUI/res/layout/fixed_layout.xml
index 0dd4a33..8414feb 100644
--- a/packages/DocumentsUI/res/layout/fixed_layout.xml
+++ b/packages/DocumentsUI/res/layout/fixed_layout.xml
@@ -27,7 +27,7 @@
android:layout_height="match_parent"
android:orientation="vertical">
- <com.android.documentsui.DocumentsToolBar
+ <com.android.documentsui.DocumentsToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
@@ -43,7 +43,7 @@
android:layout_marginStart="4dp"
android:overlapAnchor="true" />
- </com.android.documentsui.DocumentsToolBar>
+ </com.android.documentsui.DocumentsToolbar>
<LinearLayout
android:layout_width="match_parent"
diff --git a/packages/DocumentsUI/res/layout/single_pane_layout.xml b/packages/DocumentsUI/res/layout/single_pane_layout.xml
index c5a5745..f53d698 100644
--- a/packages/DocumentsUI/res/layout/single_pane_layout.xml
+++ b/packages/DocumentsUI/res/layout/single_pane_layout.xml
@@ -27,7 +27,7 @@
android:layout_height="match_parent"
android:orientation="vertical">
- <com.android.documentsui.DocumentsToolBar
+ <com.android.documentsui.DocumentsToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
@@ -43,7 +43,7 @@
android:layout_marginStart="4dp"
android:overlapAnchor="true" />
- </com.android.documentsui.DocumentsToolBar>
+ </com.android.documentsui.DocumentsToolbar>
<include layout="@layout/directory_cluster"/>
diff --git a/packages/DocumentsUI/res/values/layouts.xml b/packages/DocumentsUI/res/values/layouts.xml
index 8ac1ac2..c9308a1 100644
--- a/packages/DocumentsUI/res/values/layouts.xml
+++ b/packages/DocumentsUI/res/values/layouts.xml
@@ -15,7 +15,7 @@
-->
<resources>
- <item name="docs_activity" type="layout">@layout/drawer_layout</item>
+ <item name="documents_activity" type="layout">@layout/drawer_layout</item>
<item name="files_activity" type="layout">@layout/drawer_layout</item>
- <item name="manage_roots_activity" type="layout">@layout/single_pane_layout</item>
+ <item name="downloads_activity" type="layout">@layout/single_pane_layout</item>
</resources>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index 497eb0e..e72343e 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -42,16 +42,10 @@
import android.support.annotation.LayoutRes;
import android.support.annotation.Nullable;
import android.util.Log;
-import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemSelectedListener;
-import android.widget.BaseAdapter;
-import android.widget.ImageView;
-import android.widget.TextView;
+import android.widget.Spinner;
+import android.widget.Toolbar;
import com.android.documentsui.RecentsProvider.ResumeColumns;
import com.android.documentsui.SearchManager.SearchManagerListener;
@@ -72,7 +66,8 @@
import java.util.List;
import java.util.concurrent.Executor;
-public abstract class BaseActivity extends Activity implements SearchManagerListener {
+public abstract class BaseActivity extends Activity
+ implements SearchManagerListener, NavigationView.Environment {
static final String EXTRA_STATE = "state";
@@ -80,7 +75,7 @@
RootsCache mRoots;
SearchManager mSearchManager;
DrawerController mDrawer;
- boolean mProductivityDevice;
+ NavigationView mNavigator;
private final String mTag;
@LayoutRes
@@ -92,7 +87,6 @@
abstract void onTaskFinished(Uri... uris);
abstract void refreshDirectory(int anim);
- abstract void updateActionBar();
abstract void saveStackBlocking();
abstract State buildState();
@@ -101,19 +95,21 @@
mTag = tag;
}
+ @CallSuper
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
+ setContentView(mLayoutId);
+
+ mDrawer = DrawerController.create(this);
mState = (icicle != null)
? icicle.<State>getParcelable(EXTRA_STATE)
: buildState();
-
Metrics.logActivityLaunch(this, mState, getIntent());
- setContentView(mLayoutId);
-
mRoots = DocumentsApplication.getRootsCache(this);
+
mRoots.setOnCacheUpdateListener(
new RootsCache.OnCacheUpdateListener() {
@Override
@@ -121,9 +117,19 @@
new HandleRootsChangedTask().execute(getCurrentRoot());
}
});
+
mDirectoryContainer = (DirectoryContainerView) findViewById(R.id.container_directory);
mSearchManager = new SearchManager(this);
+ DocumentsToolbar toolbar = (DocumentsToolbar) findViewById(R.id.toolbar);
+ setActionBar(toolbar);
+ mNavigator = new NavigationView(
+ mDrawer,
+ toolbar,
+ (Spinner) findViewById(R.id.stack),
+ mState,
+ this);
+
// Base classes must update result in their onCreate.
setResult(Activity.RESULT_CANCELED);
}
@@ -133,7 +139,7 @@
boolean showMenu = super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.activity, menu);
- mSearchManager.install((DocumentsToolBar) findViewById(R.id.toolbar));
+ mSearchManager.install((DocumentsToolbar) findViewById(R.id.toolbar));
return showMenu;
}
@@ -201,6 +207,11 @@
void onStackRestored(boolean restored, boolean external) {}
void onRootPicked(RootInfo root) {
+ // Skip refreshing if root didn't change
+ if(root.equals(getCurrentRoot())) {
+ return;
+ }
+
mState.derivedMode = LocalPreferences.getViewMode(this, root, MODE_GRID);
// Clear entire backstack and start in new root
@@ -336,7 +347,8 @@
* The current directory name and selection will get updated.
* @param anim
*/
- final void refreshCurrentRootAndDirectory(int anim) {
+ @Override
+ public final void refreshCurrentRootAndDirectory(int anim) {
mSearchManager.cancelSearch();
mDirectoryContainer.setDrawDisappearingFirst(anim == ANIM_ENTER);
@@ -347,8 +359,7 @@
roots.onCurrentRootChanged();
}
- updateActionBar();
-
+ mNavigator.update();
invalidateOptionsMenu();
}
@@ -484,6 +495,12 @@
super.onRestoreInstanceState(state);
}
+ @Override
+ public boolean isSearchExpanded() {
+ return mSearchManager.isExpanded();
+ }
+
+ @Override
public RootInfo getCurrentRoot() {
if (mState.stack.root != null) {
return mState.stack.root;
@@ -692,92 +709,6 @@
}
}
- final class ItemSelectedListener implements OnItemSelectedListener {
-
- boolean mIgnoreNextNavigation;
-
- @Override
- public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
- if (mIgnoreNextNavigation) {
- mIgnoreNextNavigation = false;
- return;
- }
-
- while (mState.stack.size() > position + 1) {
- mState.popDocument();
- }
- refreshCurrentRootAndDirectory(ANIM_LEAVE);
- }
-
- @Override
- public void onNothingSelected(AdapterView<?> parent) {
- // Ignored
- }
- }
-
- /**
- * Class providing toolbar with runtime access to useful activity data.
- */
- final class StackAdapter extends BaseAdapter {
- @Override
- public int getCount() {
- return mState.stack.size();
- }
-
- @Override
- public DocumentInfo getItem(int position) {
- return mState.stack.get(mState.stack.size() - position - 1);
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.item_subdir_title, parent, false);
- }
-
- final TextView title = (TextView) convertView.findViewById(android.R.id.title);
- final DocumentInfo doc = getItem(position);
-
- if (position == 0) {
- final RootInfo root = getCurrentRoot();
- title.setText(root.title);
- } else {
- title.setText(doc.displayName);
- }
-
- return convertView;
- }
-
- @Override
- public View getDropDownView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.item_subdir, parent, false);
- }
-
- final ImageView subdir = (ImageView) convertView.findViewById(R.id.subdir);
- final TextView title = (TextView) convertView.findViewById(android.R.id.title);
- final DocumentInfo doc = getItem(position);
-
- if (position == 0) {
- final RootInfo root = getCurrentRoot();
- title.setText(root.title);
- subdir.setVisibility(View.GONE);
- } else {
- title.setText(doc.displayName);
- subdir.setVisibility(View.VISIBLE);
- }
-
- return convertView;
- }
- }
-
/**
* Interface providing access to current view of documents
* even when all documents are not homed to the same parent.
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index b933d0a..815ff3d 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -43,10 +43,6 @@
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
-import android.view.View;
-import android.widget.BaseAdapter;
-import android.widget.Spinner;
-import android.widget.Toolbar;
import com.android.documentsui.RecentsProvider.RecentColumns;
import com.android.documentsui.RecentsProvider.ResumeColumns;
@@ -63,16 +59,8 @@
private static final int CODE_FORWARD = 42;
private static final String TAG = "DocumentsActivity";
- private Toolbar mToolbar;
- private Spinner mToolbarStack;
-
- private Toolbar mRootsToolbar;
-
- private ItemSelectedListener mStackListener;
- private BaseAdapter mStackAdapter;
-
public DocumentsActivity() {
- super(R.layout.docs_activity, TAG);
+ super(R.layout.documents_activity, TAG);
}
@Override
@@ -81,18 +69,6 @@
final Resources res = getResources();
- mDrawer = DrawerController.create(this);
- mToolbar = (Toolbar) findViewById(R.id.toolbar);
-
- mStackAdapter = new StackAdapter();
- mStackListener = new ItemSelectedListener();
- mToolbarStack = (Spinner) findViewById(R.id.stack);
- mToolbarStack.setOnItemSelectedListener(mStackListener);
-
- mRootsToolbar = (Toolbar) findViewById(R.id.roots_toolbar);
-
- setActionBar(mToolbar);
-
if (mState.action == ACTION_CREATE) {
final String mimeType = getIntent().getType();
final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE);
@@ -180,7 +156,7 @@
}
if (showDrawer) {
- setRootsDrawerOpen(true);
+ mNavigator.revealRootsDrawer(true);
}
}
@@ -217,66 +193,28 @@
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
- mDrawer.syncState();
- updateActionBar();
- }
-
- public void setRootsDrawerOpen(boolean open) {
- mDrawer.setOpen(open);
+ mDrawer.update();
+ mNavigator.update();
}
@Override
- public void updateActionBar() {
- if (mRootsToolbar != null) {
- final String prompt = getIntent().getStringExtra(DocumentsContract.EXTRA_PROMPT);
- if (prompt != null) {
- mRootsToolbar.setTitle(prompt);
+ public String getDrawerTitle() {
+ String title = getIntent().getStringExtra(DocumentsContract.EXTRA_PROMPT);
+ if (title == null) {
+ if (mState.action == ACTION_OPEN ||
+ mState.action == ACTION_GET_CONTENT ||
+ mState.action == ACTION_OPEN_TREE) {
+ title = getResources().getString(R.string.title_open);
+ } else if (mState.action == ACTION_CREATE ||
+ mState.action == ACTION_PICK_COPY_DESTINATION) {
+ title = getResources().getString(R.string.title_save);
} else {
- if (mState.action == ACTION_OPEN ||
- mState.action == ACTION_GET_CONTENT ||
- mState.action == ACTION_OPEN_TREE) {
- mRootsToolbar.setTitle(R.string.title_open);
- } else if (mState.action == ACTION_CREATE ||
- mState.action == ACTION_PICK_COPY_DESTINATION) {
- mRootsToolbar.setTitle(R.string.title_save);
- }
+ // If all else fails, just call it "Files".
+ title = getResources().getString(R.string.files_label);
}
}
- if (mDrawer.isUnlocked()) {
- mToolbar.setNavigationIcon(R.drawable.ic_hamburger);
- mToolbar.setNavigationContentDescription(R.string.drawer_open);
- mToolbar.setNavigationOnClickListener(
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- setRootsDrawerOpen(true);
- }
- });
- } else {
- mToolbar.setNavigationIcon(null);
- mToolbar.setNavigationContentDescription(R.string.drawer_open);
- mToolbar.setNavigationOnClickListener(null);
- }
-
- if (mSearchManager.isExpanded()) {
- mToolbar.setTitle(null);
- mToolbarStack.setVisibility(View.GONE);
- mToolbarStack.setAdapter(null);
- } else {
- if (mState.stack.size() <= 1) {
- mToolbar.setTitle(getCurrentRoot().title);
- mToolbarStack.setVisibility(View.GONE);
- mToolbarStack.setAdapter(null);
- } else {
- mToolbar.setTitle(null);
- mToolbarStack.setVisibility(View.VISIBLE);
- mToolbarStack.setAdapter(mStackAdapter);
-
- mStackListener.mIgnoreNextNavigation = true;
- mToolbarStack.setSelection(mStackAdapter.getCount() - 1);
- }
- }
+ return title;
}
@Override
@@ -331,11 +269,6 @@
}
@Override
- public boolean onOptionsItemSelected(MenuItem item) {
- return mDrawer.onOptionsItemSelected(item) || super.onOptionsItemSelected(item);
- }
-
- @Override
void refreshDirectory(int anim) {
final FragmentManager fm = getFragmentManager();
final RootInfo root = getCurrentRoot();
@@ -396,7 +329,11 @@
@Override
void onRootPicked(RootInfo root) {
super.onRootPicked(root);
- setRootsDrawerOpen(false);
+ mNavigator.revealRootsDrawer(false);
+ }
+
+ public void setRootsDrawerOpen(boolean open) {
+ mNavigator.revealRootsDrawer(open);
}
@Override
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsToolBar.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsToolbar.java
similarity index 86%
rename from packages/DocumentsUI/src/com/android/documentsui/DocumentsToolBar.java
rename to packages/DocumentsUI/src/com/android/documentsui/DocumentsToolbar.java
index 36b7646..7742cbf 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsToolBar.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsToolbar.java
@@ -24,28 +24,28 @@
/**
* ToolBar of Documents UI.
*/
-public class DocumentsToolBar extends Toolbar {
+public class DocumentsToolbar extends Toolbar {
interface OnActionViewCollapsedListener {
void onActionViewCollapsed();
}
private OnActionViewCollapsedListener mOnActionViewCollapsedListener;
- public DocumentsToolBar(Context context, AttributeSet attrs,
+ public DocumentsToolbar(Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
- public DocumentsToolBar(Context context, AttributeSet attrs,
+ public DocumentsToolbar(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
- public DocumentsToolBar(Context context, AttributeSet attrs) {
+ public DocumentsToolbar(Context context, AttributeSet attrs) {
super(context, attrs);
}
- public DocumentsToolBar(Context context) {
+ public DocumentsToolbar(Context context) {
super(context);
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java
index 5cc677c..89be910 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java
@@ -35,9 +35,6 @@
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
-import android.view.View;
-import android.widget.BaseAdapter;
-import android.widget.Spinner;
import android.widget.Toolbar;
import com.android.documentsui.RecentsProvider.ResumeColumns;
@@ -56,14 +53,8 @@
public class DownloadsActivity extends BaseActivity {
private static final String TAG = "DownloadsActivity";
- private Toolbar mToolbar;
- private Spinner mToolbarStack;
-
- private ItemSelectedListener mStackListener;
- private BaseAdapter mStackAdapter;
-
public DownloadsActivity() {
- super(R.layout.manage_roots_activity, TAG);
+ super(R.layout.downloads_activity, TAG);
}
@Override
@@ -72,19 +63,10 @@
final Context context = this;
- mDrawer = DrawerController.createDummy();
-
- mToolbar = (Toolbar) findViewById(R.id.toolbar);
- mToolbar.setTitleTextAppearance(context,
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ toolbar.setTitleTextAppearance(context,
android.R.style.TextAppearance_DeviceDefault_Widget_ActionBar_Title);
- mStackAdapter = new StackAdapter();
- mStackListener = new ItemSelectedListener();
- mToolbarStack = (Spinner) findViewById(R.id.stack);
- mToolbarStack.setOnItemSelectedListener(mStackListener);
-
- setActionBar(mToolbar);
-
if (!mState.restored) {
// In this case, we set the activity title in AsyncTask.onPostExecute(). To prevent
// talkback from reading aloud the default title, we clear it here.
@@ -112,33 +94,12 @@
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
- updateActionBar();
+ mNavigator.update();
}
@Override
- public void updateActionBar() {
- // No navigation in manage root mode.
- mToolbar.setNavigationIcon(null);
- mToolbar.setNavigationOnClickListener(null);
-
- if (mSearchManager.isExpanded()) {
- mToolbar.setTitle(null);
- mToolbarStack.setVisibility(View.GONE);
- mToolbarStack.setAdapter(null);
- } else {
- if (mState.stack.size() <= 1) {
- mToolbar.setTitle(getCurrentRoot().title);
- mToolbarStack.setVisibility(View.GONE);
- mToolbarStack.setAdapter(null);
- } else {
- mToolbar.setTitle(null);
- mToolbarStack.setVisibility(View.VISIBLE);
- mToolbarStack.setAdapter(mStackAdapter);
-
- mStackListener.mIgnoreNextNavigation = true;
- mToolbarStack.setSelection(mStackAdapter.getCount() - 1);
- }
- }
+ public String getDrawerTitle() {
+ return null; // being and nothingness
}
@Override
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java b/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java
index df3ac1b..bcf69c4 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java
@@ -22,8 +22,8 @@
import android.support.v4.app.ActionBarDrawerToggle;
import android.support.v4.widget.DrawerLayout;
import android.support.v4.widget.DrawerLayout.DrawerListener;
-import android.view.MenuItem;
import android.view.View;
+import android.widget.Toolbar;
/**
* A facade over the various pieces comprising "roots fragment in a Drawer".
@@ -33,13 +33,10 @@
abstract class DrawerController implements DrawerListener {
abstract void setOpen(boolean open);
- abstract void lockOpen();
- abstract void lockClosed();
abstract boolean isPresent();
abstract boolean isOpen();
- abstract boolean isUnlocked();
- abstract void syncState();
- abstract boolean onOptionsItemSelected(MenuItem item);
+ abstract void setTitle(String title);
+ abstract void update();
/**
* Returns a controller suitable for {@code Layout}.
@@ -53,6 +50,8 @@
}
View drawer = activity.findViewById(R.id.drawer_roots);
+ Toolbar toolbar = (Toolbar) activity.findViewById(R.id.roots_toolbar);
+
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
activity,
layout,
@@ -60,7 +59,7 @@
R.string.drawer_open,
R.string.drawer_close);
- return new RuntimeDrawerController(layout, drawer, toggle);
+ return new RuntimeDrawerController(layout, drawer, toggle, toolbar);
}
/**
@@ -78,9 +77,12 @@
private final ActionBarDrawerToggle mToggle;
private DrawerLayout mLayout;
private View mDrawer;
+ private Toolbar mToolbar;
public RuntimeDrawerController(
- DrawerLayout layout, View drawer, ActionBarDrawerToggle toggle) {
+ DrawerLayout layout, View drawer, ActionBarDrawerToggle toggle,
+ Toolbar drawerToolbar) {
+ mToolbar = drawerToolbar;
checkArgument(layout != null);
mLayout = layout;
@@ -110,31 +112,16 @@
}
@Override
- void syncState() {
+ void setTitle(String title) {
+ mToolbar.setTitle(title);
+ }
+
+ @Override
+ void update() {
mToggle.syncState();
}
@Override
- boolean isUnlocked() {
- return mLayout.getDrawerLockMode(mDrawer) == DrawerLayout.LOCK_MODE_UNLOCKED;
- }
-
- @Override
- void lockOpen() {
- mLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN);
- }
-
- @Override
- void lockClosed() {
- mLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
- }
-
- @Override
- boolean onOptionsItemSelected(MenuItem item) {
- return false;
- }
-
- @Override
public void onDrawerSlide(View drawerView, float slideOffset) {
mToggle.onDrawerSlide(drawerView, slideOffset);
}
@@ -163,14 +150,6 @@
@Override
void setOpen(boolean open) {}
- @Override
- void syncState() {}
-
- @Override
- void lockOpen() {}
-
- @Override
- void lockClosed() {}
@Override
boolean isOpen() {
@@ -178,19 +157,15 @@
}
@Override
- boolean isUnlocked() {
- return true;
- }
-
- @Override
boolean isPresent() {
return false;
}
@Override
- boolean onOptionsItemSelected(MenuItem item) {
- return false;
- }
+ void setTitle(String title) {}
+
+ @Override
+ void update() {}
@Override
public void onDrawerSlide(View drawerView, float slideOffset) {}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
index 064535a..3aba356 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
@@ -40,10 +40,6 @@
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
-import android.view.View;
-import android.widget.BaseAdapter;
-import android.widget.Spinner;
-import android.widget.Toolbar;
import com.android.documentsui.OperationDialogFragment.DialogType;
import com.android.documentsui.RecentsProvider.ResumeColumns;
@@ -67,10 +63,6 @@
public static final String TAG = "FilesActivity";
- private Toolbar mToolbar;
- private Spinner mToolbarStack;
- private ItemSelectedListener mStackListener;
- private BaseAdapter mStackAdapter;
private DocumentClipper mClipper;
public FilesActivity() {
@@ -81,17 +73,7 @@
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
- mToolbar = (Toolbar) findViewById(R.id.toolbar);
-
- mStackAdapter = new StackAdapter();
- mStackListener = new ItemSelectedListener();
- mToolbarStack = (Spinner) findViewById(R.id.stack);
- mToolbarStack.setOnItemSelectedListener(mStackListener);
-
- setActionBar(mToolbar);
-
mClipper = new DocumentClipper(this);
- mDrawer = DrawerController.create(this);
RootsFragment.show(getFragmentManager(), null);
@@ -179,11 +161,11 @@
// serach. Why? Because this avoid an early (undesired) load of
// the recents root...which is the default root in other activities.
// In Files app "Home" is the default, but it is loaded async.
- // updateActionBar will be called once Home root is loaded.
+ // update will be called once Home root is loaded.
// Except while searching we need this call to ensure the
// search bits get layed out correctly.
if (mSearchManager.isSearching()) {
- updateActionBar();
+ mNavigator.update();
}
}
@@ -203,44 +185,8 @@
}
@Override
- public void updateActionBar() {
- final RootInfo root = getCurrentRoot();
-
- if (mDrawer.isPresent()) {
- mToolbar.setNavigationIcon(R.drawable.ic_hamburger);
- mToolbar.setNavigationContentDescription(R.string.drawer_open);
- mToolbar.setNavigationOnClickListener(
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- mDrawer.setOpen(true);
- }
- });
- } else {
- mToolbar.setNavigationIcon(
- root != null ? root.loadToolbarIcon(mToolbar.getContext()) : null);
- mToolbar.setNavigationContentDescription(R.string.drawer_open);
- mToolbar.setNavigationOnClickListener(null);
- }
-
- if (mSearchManager.isExpanded()) {
- mToolbar.setTitle(null);
- mToolbarStack.setVisibility(View.GONE);
- mToolbarStack.setAdapter(null);
- } else {
- if (mState.stack.size() <= 1) {
- mToolbar.setTitle(root.title);
- mToolbarStack.setVisibility(View.GONE);
- mToolbarStack.setAdapter(null);
- } else {
- mToolbar.setTitle(null);
- mToolbarStack.setVisibility(View.VISIBLE);
- mToolbarStack.setAdapter(mStackAdapter);
-
- mStackListener.mIgnoreNextNavigation = true;
- mToolbarStack.setSelection(mStackAdapter.getCount() - 1);
- }
- }
+ public String getDrawerTitle() {
+ return getResources().getString(R.string.files_label);
}
@Override
diff --git a/packages/DocumentsUI/src/com/android/documentsui/NavigationView.java b/packages/DocumentsUI/src/com/android/documentsui/NavigationView.java
new file mode 100644
index 0000000..ff1940a
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/NavigationView.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui;
+
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+import static com.android.documentsui.Shared.DEBUG;
+import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_LEAVE;
+
+import android.annotation.Nullable;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import com.android.documentsui.model.DocumentInfo;
+import com.android.documentsui.model.RootInfo;
+
+/**
+ * A facade over the portions of the app and drawer toolbars.
+ */
+class NavigationView {
+
+ private static final String TAG = "NavigationView";
+
+ private final DrawerController mDrawer;
+ private final DocumentsToolbar mToolbar;
+ private final Spinner mBreadcrumb;
+ private final State mState;
+ private final NavigationView.Environment mEnv;
+ private final BreadcrumbAdapter mBreadcrumbAdapter;
+
+ private boolean mIgnoreNextNavigation;
+
+ public NavigationView(
+ DrawerController drawer,
+ DocumentsToolbar toolbar,
+ Spinner breadcrumb,
+ State state,
+ NavigationView.Environment env) {
+
+ mToolbar = toolbar;
+ mBreadcrumb = breadcrumb;
+ mDrawer = drawer;
+ mState = state;
+ mEnv = env;
+
+ mBreadcrumbAdapter = new BreadcrumbAdapter(mState, mEnv);
+ mToolbar.setNavigationOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ onNavigationIconClicked();
+ }
+
+ });
+
+ mBreadcrumb.setOnItemSelectedListener(
+ new OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(
+ AdapterView<?> parent, View view, int position, long id) {
+ onBreadcrumbItemSelected(position);
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {}
+ });
+
+ }
+
+ private void onNavigationIconClicked() {
+ if (mDrawer.isPresent()) {
+ mDrawer.setOpen(true);
+ }
+ }
+
+ private void onBreadcrumbItemSelected(int position) {
+ if (mIgnoreNextNavigation) {
+ mIgnoreNextNavigation = false;
+ return;
+ }
+
+ while (mState.stack.size() > position + 1) {
+ mState.popDocument();
+ }
+ mEnv.refreshCurrentRootAndDirectory(ANIM_LEAVE);
+ }
+
+ void update() {
+
+ // TODO: Looks to me like this block is never getting hit.
+ if (mEnv.isSearchExpanded()) {
+ mToolbar.setTitle(null);
+ mBreadcrumb.setVisibility(View.GONE);
+ mBreadcrumb.setAdapter(null);
+ return;
+ }
+
+ mDrawer.setTitle(mEnv.getDrawerTitle());
+
+ mToolbar.setNavigationIcon(getActionBarIcon());
+ mToolbar.setNavigationContentDescription(R.string.drawer_open);
+
+ if (mState.stack.size() <= 1) {
+ showBreadcrumb(false);
+ String title = mEnv.getCurrentRoot().title;
+ if (DEBUG) Log.d(TAG, "New toolbar title is: " + title);
+ mToolbar.setTitle(title);
+ } else {
+ showBreadcrumb(true);
+ mToolbar.setTitle(null);
+ mIgnoreNextNavigation = true;
+ mBreadcrumb.setSelection(mBreadcrumbAdapter.getCount() - 1);
+ }
+
+ if (DEBUG) Log.d(TAG, "Final toolbar title is: " + mToolbar.getTitle());
+ }
+
+ private void showBreadcrumb(boolean visibility) {
+ if (visibility) {
+ mBreadcrumb.setVisibility(VISIBLE);
+ mBreadcrumb.setAdapter(mBreadcrumbAdapter);
+ } else {
+ mBreadcrumb.setVisibility(GONE);
+ mBreadcrumb.setAdapter(null);
+ }
+ }
+
+ // Hamburger if drawer is present, else root icon, or sad nullness.
+ private @Nullable Drawable getActionBarIcon() {
+ if (mDrawer.isPresent()) {
+ return mToolbar.getContext().getDrawable(R.drawable.ic_hamburger);
+ } else {
+ RootInfo root = mEnv.getCurrentRoot();
+ if (root != null) {
+ return root.loadToolbarIcon(mToolbar.getContext());
+ }
+ }
+ return null;
+ }
+
+ void revealRootsDrawer(boolean open) {
+ mDrawer.setOpen(open);
+ }
+
+ /**
+ * Class providing toolbar with runtime access to useful activity data.
+ */
+ static final class BreadcrumbAdapter extends BaseAdapter {
+
+ private Environment mEnv;
+ private State mState;
+
+ public BreadcrumbAdapter(State state, Environment env) {
+ mState = state;
+ mEnv = env;
+ }
+
+ @Override
+ public int getCount() {
+ return mState.stack.size();
+ }
+
+ @Override
+ public DocumentInfo getItem(int position) {
+ return mState.stack.get(mState.stack.size() - position - 1);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.item_subdir_title, parent, false);
+ }
+
+ final TextView title = (TextView) convertView.findViewById(android.R.id.title);
+ final DocumentInfo doc = getItem(position);
+
+ if (position == 0) {
+ final RootInfo root = mEnv.getCurrentRoot();
+ title.setText(root.title);
+ } else {
+ title.setText(doc.displayName);
+ }
+
+ return convertView;
+ }
+
+ @Override
+ public View getDropDownView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.item_subdir, parent, false);
+ }
+
+ final ImageView subdir = (ImageView) convertView.findViewById(R.id.subdir);
+ final TextView title = (TextView) convertView.findViewById(android.R.id.title);
+ final DocumentInfo doc = getItem(position);
+
+ if (position == 0) {
+ final RootInfo root = mEnv.getCurrentRoot();
+ title.setText(root.title);
+ subdir.setVisibility(View.GONE);
+ } else {
+ title.setText(doc.displayName);
+ subdir.setVisibility(View.VISIBLE);
+ }
+
+ return convertView;
+ }
+ }
+
+ interface Environment {
+ RootInfo getCurrentRoot();
+ String getDrawerTitle();
+ void refreshCurrentRootAndDirectory(int animation);
+ boolean isSearchExpanded();
+ }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
index c64953d..26bda312 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
@@ -180,9 +180,9 @@
Item item = mAdapter.getItem(position);
if (item instanceof RootItem) {
BaseActivity activity = BaseActivity.get(RootsFragment.this);
- RootInfo info = ((RootItem) item).root;
- Metrics.logRootVisited(getActivity(), info);
- activity.onRootPicked(info);
+ RootInfo newRoot = ((RootItem) item).root;
+ Metrics.logRootVisited(getActivity(), newRoot);
+ activity.onRootPicked(newRoot);
} else if (item instanceof AppItem) {
DocumentsActivity activity = DocumentsActivity.get(RootsFragment.this);
ResolveInfo info = ((AppItem) item).info;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/SearchManager.java b/packages/DocumentsUI/src/com/android/documentsui/SearchManager.java
index fb585a6..69f54c7 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/SearchManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/SearchManager.java
@@ -47,7 +47,7 @@
private boolean mSearchExpanded;
private boolean mIgnoreNextClose;
- private DocumentsToolBar mActionBar;
+ private DocumentsToolbar mActionBar;
private MenuItem mMenu;
private SearchView mView;
@@ -59,7 +59,7 @@
mListener = listener;
}
- public void install(DocumentsToolBar actionBar) {
+ public void install(DocumentsToolbar actionBar) {
assert (mActionBar == null);
mActionBar = actionBar;
mMenu = actionBar.getSearchMenu();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 5f04eac..1e3da6b 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -100,6 +100,7 @@
import com.android.documentsui.model.RootInfo;
import com.android.documentsui.services.FileOperationService;
import com.android.documentsui.services.FileOperations;
+
import com.google.common.collect.Lists;
import java.util.ArrayList;
@@ -151,7 +152,6 @@
private String mStateKey;
private int mLastSortOrder = SORT_ORDER_UNKNOWN;
- private boolean mLastShowSize;
private DocumentsAdapter mAdapter;
private LoaderCallbacks<DirectoryResult> mCallbacks;
private FragmentTuner mTuner;
@@ -428,7 +428,6 @@
private void updateDisplayState() {
State state = getDisplayState();
- mLastShowSize = state.showSize;
updateLayout(state.derivedMode);
mRecView.setAdapter(mAdapter);
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java
index 0a91427..243c357 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java
@@ -30,7 +30,7 @@
import android.support.test.uiautomator.Configurator;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.Until;
-import android.test.InstrumentationTestCase;
+import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.LargeTest;
import android.util.Log;
import android.view.MotionEvent;
@@ -38,7 +38,7 @@
import com.android.documentsui.model.RootInfo;
@LargeTest
-public class DownloadsActivityUiTest extends InstrumentationTestCase {
+public class DownloadsActivityUiTest extends ActivityInstrumentationTestCase2<DownloadsActivity> {
private static final int TIMEOUT = 5000;
private static final String TAG = "DownloadsActivityUiTest";
@@ -53,6 +53,10 @@
private ContentProviderClient mClient;
private RootInfo mRoot;
+ public DownloadsActivityUiTest() {
+ super(DownloadsActivity.class);
+ }
+
public void setUp() throws Exception {
// Initialize UiDevice instance.
Instrumentation instrumentation = getInstrumentation();
@@ -79,7 +83,8 @@
intent.setDataAndType(mRoot.getUri(), DocumentsContract.Root.MIME_TYPE_ITEM);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
- mContext.startActivity(intent);
+ setActivityIntent(intent);
+ getActivity(); // Start the activity.
// Wait for the app to appear.
mDevice.wait(Until.hasObject(By.pkg(TARGET_PKG).depth(0)), TIMEOUT);
@@ -92,11 +97,11 @@
@Override
protected void tearDown() throws Exception {
- // Need to kill off the task we started.
- super.tearDown();
Log.d(TAG, "Resetting storage from setUp");
resetStorage();
mClient.release();
+
+ super.tearDown();
}
private void resetStorage() throws RemoteException {
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
index 2535a80e..868dbbb 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
@@ -30,7 +30,7 @@
import android.support.test.uiautomator.Configurator;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.Until;
-import android.test.InstrumentationTestCase;
+import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.LargeTest;
import android.util.Log;
import android.view.MotionEvent;
@@ -38,7 +38,7 @@
import com.android.documentsui.model.RootInfo;
@LargeTest
-public class FilesActivityUiTest extends InstrumentationTestCase {
+public class FilesActivityUiTest extends ActivityInstrumentationTestCase2<FilesActivity> {
private static final int TIMEOUT = 5000;
private static final String TAG = "FilesActivityUiTest";
@@ -54,6 +54,10 @@
private RootInfo mRoot_0;
private RootInfo mRoot_1;
+ public FilesActivityUiTest() {
+ super(FilesActivity.class);
+ }
+
public void setUp() throws Exception {
// Initialize UiDevice instance.
Instrumentation instrumentation = getInstrumentation();
@@ -71,12 +75,14 @@
mResolver = mContext.getContentResolver();
mClient = mResolver.acquireUnstableContentProviderClient(DEFAULT_AUTHORITY);
+ assertNotNull("Failed to acquire ContentProviderClient.", mClient);
mDocsHelper = new DocumentsProviderHelper(DEFAULT_AUTHORITY, mClient);
// Launch app.
Intent intent = mContext.getPackageManager().getLaunchIntentForPackage(TARGET_PKG);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
- mContext.startActivity(intent);
+ setActivityIntent(intent);
+ getActivity(); // Start the activity.
// Wait for the app to appear.
mDevice.wait(Until.hasObject(By.pkg(TARGET_PKG).depth(0)), TIMEOUT);
@@ -89,10 +95,11 @@
@Override
protected void tearDown() throws Exception {
- super.tearDown();
Log.d(TAG, "Resetting storage from setUp");
resetStorage();
mClient.release();
+
+ super.tearDown();
}
private void resetStorage() throws RemoteException {
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
index 9855427..2527650 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
@@ -16,10 +16,6 @@
package com.android.documentsui;
-import static com.android.documentsui.Shared.TAG;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ProviderInfo;
@@ -471,6 +467,14 @@
@Override
public Bundle call(String method, String arg, Bundle extras) {
+ // We're not supposed to override any of the default DocumentsProvider
+ // methods that are supported by "call", so javadoc asks that we
+ // always call super.call first and return if response is not null.
+ Bundle result = super.call(method, arg, extras);
+ if (result != null) {
+ return result;
+ }
+
switch (method) {
case "clear":
clearCacheAndBuildRoots();
@@ -484,11 +488,10 @@
simulateReadErrorsForFile(arg);
return null;
case "createDocumentWithFlags":
- Bundle bundle = dispatchCreateDocumentWithFlags(extras);
- return bundle;
- default:
- return super.call(method, arg, extras);
+ return dispatchCreateDocumentWithFlags(extras);
}
+
+ return null;
}
private Bundle createVirtualFileFromBundle(Bundle extras) {
diff --git a/packages/ExtServices/Android.mk b/packages/ExtServices/Android.mk
new file mode 100644
index 0000000..e8a4007
--- /dev/null
+++ b/packages/ExtServices/Android.mk
@@ -0,0 +1,40 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := ExtServices
+
+LOCAL_CERTIFICATE := platform
+
+LOCAL_AAPT_FLAGS := --shared-lib
+
+LOCAL_EXPORT_PACKAGE_RESOURCES := true
+
+LOCAL_PROGUARD_FLAG_FILES := proguard.proguard
+
+LOCAL_PRIVILEGED_MODULE := true
+
+include $(BUILD_PACKAGE)
+
+
+
+
+
diff --git a/packages/ExtServices/AndroidManifest.xml b/packages/ExtServices/AndroidManifest.xml
new file mode 100644
index 0000000..100b35c
--- /dev/null
+++ b/packages/ExtServices/AndroidManifest.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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.ext.services"
+ android:versionCode="1"
+ android:versionName="1"
+ coreApp="true">
+
+ <application android:label="@string/app_name"
+ android:allowBackup="false"
+ android:forceDeviceEncrypted="true"
+ android:encryptionAware="true">
+
+ <library android:name="android.ext.services"/>
+
+ </application>
+
+</manifest>
diff --git a/packages/ExtServices/MODULE_LICENSE_APACHE2 b/packages/ExtServices/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/ExtServices/MODULE_LICENSE_APACHE2
diff --git a/packages/ExtServices/NOTICE b/packages/ExtServices/NOTICE
new file mode 100644
index 0000000..c5b1efa
--- /dev/null
+++ b/packages/ExtServices/NOTICE
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2005-2008, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+
+ 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.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/packages/ExtServices/proguard.proguard b/packages/ExtServices/proguard.proguard
new file mode 100644
index 0000000..e5dfbe1
--- /dev/null
+++ b/packages/ExtServices/proguard.proguard
@@ -0,0 +1,7 @@
+-keepparameternames
+-keepattributes Exceptions,InnerClasses,Signature,Deprecated,
+ SourceFile,LineNumberTable,*Annotation*,EnclosingMethod
+
+-keep public class * {
+ public protected *;
+}
diff --git a/packages/ExtServices/res/values/strings.xml b/packages/ExtServices/res/values/strings.xml
new file mode 100644
index 0000000..531e517
--- /dev/null
+++ b/packages/ExtServices/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name">Android Services Library</string>
+</resources>
diff --git a/packages/ExtServices/src/android/ext/services/Version.java b/packages/ExtServices/src/android/ext/services/Version.java
new file mode 100644
index 0000000..026cccd
--- /dev/null
+++ b/packages/ExtServices/src/android/ext/services/Version.java
@@ -0,0 +1,36 @@
+/*
+ * 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.ext.services;
+
+/**
+ * Class that provides the version of the library.
+ */
+public final class Version {
+
+ private Version() {
+ /* do nothing - hide constructor */
+ }
+
+ /**
+ * Gets the version of the library.
+ *
+ * @return The version.
+ */
+ public static int getVersionCode() {
+ return 1;
+ }
+}
\ No newline at end of file
diff --git a/packages/ExtShared/Android.mk b/packages/ExtShared/Android.mk
new file mode 100644
index 0000000..d8052df
--- /dev/null
+++ b/packages/ExtShared/Android.mk
@@ -0,0 +1,33 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := ExtShared
+
+LOCAL_CERTIFICATE := platform
+
+LOCAL_AAPT_FLAGS := --shared-lib
+
+LOCAL_EXPORT_PACKAGE_RESOURCES := true
+
+LOCAL_PROGUARD_FLAG_FILES := proguard.proguard
+
+include $(BUILD_PACKAGE)
\ No newline at end of file
diff --git a/packages/ExtShared/AndroidManifest.xml b/packages/ExtShared/AndroidManifest.xml
new file mode 100644
index 0000000..e2e81d0
--- /dev/null
+++ b/packages/ExtShared/AndroidManifest.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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.ext.shared"
+ android:versionCode="1"
+ android:versionName="1"
+ coreApp="true">
+
+ <application android:label="@string/app_name"
+ android:allowBackup="false"
+ android:forceDeviceEncrypted="true"
+ android:encryptionAware="true">
+
+ <library android:name="android.ext.shared"/>
+
+ </application>
+
+</manifest>
diff --git a/packages/ExtShared/MODULE_LICENSE_APACHE2 b/packages/ExtShared/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/ExtShared/MODULE_LICENSE_APACHE2
diff --git a/packages/ExtShared/NOTICE b/packages/ExtShared/NOTICE
new file mode 100644
index 0000000..c5b1efa
--- /dev/null
+++ b/packages/ExtShared/NOTICE
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2005-2008, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+
+ 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.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/packages/ExtShared/proguard.proguard b/packages/ExtShared/proguard.proguard
new file mode 100644
index 0000000..e5dfbe1
--- /dev/null
+++ b/packages/ExtShared/proguard.proguard
@@ -0,0 +1,7 @@
+-keepparameternames
+-keepattributes Exceptions,InnerClasses,Signature,Deprecated,
+ SourceFile,LineNumberTable,*Annotation*,EnclosingMethod
+
+-keep public class * {
+ public protected *;
+}
diff --git a/packages/ExtShared/res/values/strings.xml b/packages/ExtShared/res/values/strings.xml
new file mode 100644
index 0000000..0914e43
--- /dev/null
+++ b/packages/ExtShared/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name">Android Shared Library</string>
+</resources>
diff --git a/packages/ExtShared/src/android/ext/shared/Version.java b/packages/ExtShared/src/android/ext/shared/Version.java
new file mode 100644
index 0000000..3ff2d41
--- /dev/null
+++ b/packages/ExtShared/src/android/ext/shared/Version.java
@@ -0,0 +1,36 @@
+/*
+ * 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.ext.shared;
+
+/**
+ * Class that provides the version of the library.
+ */
+public final class Version {
+
+ private Version() {
+ /* do nothing - hide constructor */
+ }
+
+ /**
+ * Gets the version of the library.
+ *
+ * @return The version.
+ */
+ public static int getVersionCode() {
+ return 1;
+ }
+}
\ No newline at end of file
diff --git a/packages/MtpDocumentsProvider/AndroidManifest.xml b/packages/MtpDocumentsProvider/AndroidManifest.xml
index 7ea54c9..2dd49ab 100644
--- a/packages/MtpDocumentsProvider/AndroidManifest.xml
+++ b/packages/MtpDocumentsProvider/AndroidManifest.xml
@@ -30,16 +30,12 @@
</activity>
<receiver android:name=".UsbIntentReceiver" android:exported="true">
- <!-- TODO: Remove an intent-filter for USB_DEVICE_ATTACHED after the provider becomes to
- open devices on demand. -->
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
+ <action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" />
</intent-filter>
<meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/device_filter" />
- <intent-filter>
- <action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" />
- </intent-filter>
</receiver>
</application>
</manifest>
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java b/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java
index 76a1fad..124d207 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java
@@ -44,11 +44,38 @@
return false;
final Identifier other = (Identifier) obj;
return mDeviceId == other.mDeviceId && mStorageId == other.mStorageId &&
- mObjectHandle == other.mObjectHandle && mDocumentId == other.mDocumentId;
+ mObjectHandle == other.mObjectHandle && mDocumentId.equals(other.mDocumentId);
}
@Override
public int hashCode() {
return Objects.hash(mDeviceId, mStorageId, mObjectHandle, mDocumentId);
}
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("Identifier { ");
+
+ builder.append("mDeviceId: ");
+ builder.append(mDeviceId);
+ builder.append(", ");
+
+ builder.append("mStorageId: ");
+ builder.append(mStorageId);
+ builder.append(", ");
+
+ builder.append("mObjectHandle: ");
+ builder.append(mObjectHandle);
+ builder.append(", ");
+
+ builder.append("mDocumentId: ");
+ builder.append(mDocumentId);
+ builder.append(", ");
+
+ builder.append("mDocumentType: ");
+ builder.append(mDocumentType);
+ builder.append(" }");
+ return builder.toString();
+ }
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
index ac47067..3faa8f4 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
@@ -20,11 +20,9 @@
import android.annotation.Nullable;
import android.content.ContentValues;
-import android.content.res.Resources;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
import android.mtp.MtpObjectInfo;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
@@ -87,12 +85,10 @@
/**
* Puts root information to database.
* @param parentDocumentId Document ID of device document.
- * @param resources Resources required to localize root name.
* @param roots List of root information.
* @return If roots are added or removed from the database.
*/
- synchronized boolean putStorageDocuments(
- String parentDocumentId, Resources resources, MtpRoot[] roots) {
+ synchronized boolean putStorageDocuments(String parentDocumentId, MtpRoot[] roots) {
final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
database.beginTransaction();
try {
@@ -117,7 +113,7 @@
valuesList[i] = new ContentValues();
extraValuesList[i] = new ContentValues();
MtpDatabase.getStorageDocumentValues(
- valuesList[i], extraValuesList[i], resources, parentDocumentId, roots[i]);
+ valuesList[i], extraValuesList[i], parentDocumentId, roots[i]);
}
final boolean changed = putDocuments(
valuesList,
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
index 59d85c1..b44f9af 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
@@ -108,7 +108,7 @@
* @param columnNames Column names defined in {@link android.provider.DocumentsContract.Root}.
* @return Database cursor.
*/
- Cursor queryRoots(String[] columnNames) {
+ Cursor queryRoots(Resources resources, String[] columnNames) {
final String selection =
COLUMN_ROW_STATE + " IN (?, ?) AND " + COLUMN_DOCUMENT_TYPE + " = ?";
final Cursor deviceCursor = mDatabase.query(
@@ -183,10 +183,14 @@
}
if (storageCursor.getCount() == 1 && values.containsKey(Root.COLUMN_TITLE)) {
storageCursor.moveToFirst();
+ // Add storage name to device name if we have only 1 storage.
values.put(
Root.COLUMN_TITLE,
- storageCursor.getString(
- storageCursor.getColumnIndex(Root.COLUMN_TITLE)));
+ resources.getString(
+ R.string.root_name,
+ values.getAsString(Root.COLUMN_TITLE),
+ storageCursor.getString(
+ storageCursor.getColumnIndex(Root.COLUMN_TITLE))));
}
} finally {
storageCursor.close();
@@ -533,13 +537,11 @@
/**
* Gets {@link ContentValues} for the given root.
* @param values {@link ContentValues} that receives values.
- * @param resources Resources used to get localized root name.
* @param root Root to be converted {@link ContentValues}.
*/
static void getStorageDocumentValues(
ContentValues values,
ContentValues extraValues,
- Resources resources,
String parentDocumentId,
MtpRoot root) {
values.clear();
@@ -550,13 +552,12 @@
values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
values.put(COLUMN_DOCUMENT_TYPE, DOCUMENT_TYPE_STORAGE);
values.put(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);
- values.put(Document.COLUMN_DISPLAY_NAME, root.getRootName(resources));
+ values.put(Document.COLUMN_DISPLAY_NAME, root.mDescription);
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_SIZE,
- (int) Math.min(root.mMaxCapacity - root.mFreeSpace, Integer.MAX_VALUE));
+ values.put(Document.COLUMN_SIZE, root.mMaxCapacity - root.mFreeSpace);
extraValues.put(
Root.COLUMN_FLAGS,
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
index 775f976..70a1aae 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
@@ -58,6 +58,8 @@
Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
};
+ static final boolean DEBUG = false;
+
private final Object mDeviceListLock = new Object();
private static MtpDocumentsProvider sSingleton;
@@ -70,6 +72,7 @@
private Resources mResources;
private MtpDatabase mDatabase;
private AppFuse mAppFuse;
+ private ServiceIntentSender mIntentSender;
/**
* Provides singleton instance to MtpDocumentsService.
@@ -86,8 +89,9 @@
mResolver = getContext().getContentResolver();
mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
- mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase);
+ mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase);
mAppFuse = new AppFuse(TAG, new AppFuseCallback());
+ mIntentSender = new ServiceIntentSender(getContext());
// TODO: Mount AppFuse on demands.
try {
mAppFuse.mount(getContext().getSystemService(StorageManager.class));
@@ -105,14 +109,16 @@
MtpManager mtpManager,
ContentResolver resolver,
MtpDatabase database,
- StorageManager storageManager) {
+ StorageManager storageManager,
+ ServiceIntentSender intentSender) {
mResources = resources;
mMtpManager = mtpManager;
mResolver = resolver;
mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
mDatabase = database;
- mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase);
+ mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase);
mAppFuse = new AppFuse(TAG, new AppFuseCallback());
+ mIntentSender = intentSender;
// TODO: Mount AppFuse on demands.
try {
mAppFuse.mount(storageManager);
@@ -129,7 +135,7 @@
if (projection == null) {
projection = MtpDocumentsProvider.DEFAULT_ROOT_PROJECTION;
}
- final Cursor cursor = mDatabase.queryRoots(projection);
+ final Cursor cursor = mDatabase.queryRoots(mResources, projection);
cursor.setNotificationUri(
mResolver, DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY));
return cursor;
@@ -147,11 +153,15 @@
@Override
public Cursor queryChildDocuments(String parentDocumentId,
String[] projection, String sortOrder) throws FileNotFoundException {
+ if (DEBUG) {
+ Log.d(TAG, "queryChildDocuments: " + parentDocumentId);
+ }
if (projection == null) {
projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
}
Identifier parentIdentifier = mDatabase.createIdentifier(parentDocumentId);
try {
+ openDevice(parentIdentifier.mDeviceId);
if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE) {
final Identifier singleStorageIdentifier =
mDatabase.getSingleStorageIdentifier(parentDocumentId);
@@ -176,11 +186,12 @@
throws FileNotFoundException {
final Identifier identifier = mDatabase.createIdentifier(documentId);
try {
+ openDevice(identifier.mDeviceId);
switch (mode) {
case "r":
final long fileSize = getFileSize(documentId);
- // MTP getPartialObject operation does not support files that are larger than 4GB.
- // Fallback to non-seekable file descriptor.
+ // MTP getPartialObject operation does not support files that are larger than
+ // 4GB. Fallback to non-seekable file descriptor.
// TODO: Use getPartialObject64 for MTP devices that support Android vendor
// extension.
if (fileSize <= 0xffffffffl) {
@@ -213,6 +224,7 @@
CancellationSignal signal) throws FileNotFoundException {
final Identifier identifier = mDatabase.createIdentifier(documentId);
try {
+ openDevice(identifier.mDeviceId);
return new AssetFileDescriptor(
getPipeManager(identifier).readThumbnail(mMtpManager, identifier),
0, // Start offset.
@@ -227,6 +239,7 @@
public void deleteDocument(String documentId) throws FileNotFoundException {
try {
final Identifier identifier = mDatabase.createIdentifier(documentId);
+ openDevice(identifier.mDeviceId);
final Identifier parentIdentifier = mDatabase.getParentIdentifier(documentId);
mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle);
mDatabase.deleteDocument(documentId);
@@ -259,6 +272,7 @@
throws FileNotFoundException {
try {
final Identifier parentId = mDatabase.createIdentifier(parentDocumentId);
+ openDevice(parentId.mDeviceId);
final ParcelFileDescriptor pipe[] = ParcelFileDescriptor.createReliablePipe();
pipe[0].close(); // 0 bytes for a new document.
final int formatCode = Document.MIME_TYPE_DIR.equals(mimeType) ?
@@ -286,11 +300,22 @@
void openDevice(int deviceId) throws IOException {
synchronized (mDeviceListLock) {
+ if (mDeviceToolkits.containsKey(deviceId)) {
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Open device " + deviceId);
+ }
mMtpManager.openDevice(deviceId);
mDeviceToolkits.put(
deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase));
+ mIntentSender.sendUpdateNotificationIntent();
+ try {
+ mRootScanner.resume().await();
+ } catch (InterruptedException error) {
+ Log.e(TAG, "openDevice", error);
+ }
}
- mRootScanner.resume();
}
void closeDevice(int deviceId) throws IOException, InterruptedException {
@@ -298,6 +323,7 @@
closeDeviceInternal(deviceId);
}
mRootScanner.resume();
+ mIntentSender.sendUpdateNotificationIntent();
}
int[] getOpenedDeviceIds() {
@@ -318,6 +344,13 @@
}
/**
+ * Resumes root scanner to handle the update of device list.
+ */
+ void resumeRootScanner() {
+ mRootScanner.resume();
+ }
+
+ /**
* Finalize the content provider for unit tests.
*/
@Override
@@ -356,6 +389,12 @@
private void closeDeviceInternal(int deviceId) throws IOException, InterruptedException {
// TODO: Flush the device before closing (if not closed externally).
+ if (!mDeviceToolkits.containsKey(deviceId)) {
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Close device " + deviceId);
+ }
getDeviceToolkit(deviceId).mDocumentLoader.clearTasks();
mDeviceToolkits.remove(deviceId);
mMtpManager.closeDevice(deviceId);
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java
index 5bede86..9c4952b 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java
@@ -19,13 +19,12 @@
import android.app.Notification;
import android.app.Service;
import android.app.NotificationManager;
+import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
-import android.hardware.usb.UsbDevice;
import android.os.IBinder;
import android.util.Log;
-import com.android.internal.util.Preconditions;
-
import java.io.IOException;
/**
@@ -36,6 +35,7 @@
public class MtpDocumentsService extends Service {
static final String ACTION_OPEN_DEVICE = "com.android.mtp.OPEN_DEVICE";
static final String ACTION_CLOSE_DEVICE = "com.android.mtp.CLOSE_DEVICE";
+ static final String ACTION_UPDATE_NOTIFICATION = "com.android.mtp.UPDATE_NOTIFICATION";
static final String EXTRA_DEVICE = "device";
NotificationManager mNotificationManager;
@@ -55,32 +55,10 @@
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// If intent is null, the service was restarted.
- if (intent != null) {
- final MtpDocumentsProvider provider = MtpDocumentsProvider.getInstance();
- final UsbDevice device = intent.<UsbDevice>getParcelableExtra(EXTRA_DEVICE);
- try {
- Preconditions.checkNotNull(device);
- switch (intent.getAction()) {
- case ACTION_OPEN_DEVICE:
- provider.openDevice(device.getDeviceId());
- break;
-
- case ACTION_CLOSE_DEVICE:
- mNotificationManager.cancel(device.getDeviceId());
- provider.closeDevice(device.getDeviceId());
- break;
-
- default:
- throw new IllegalArgumentException("Received unknown intent action.");
- }
- } catch (IOException | InterruptedException | IllegalArgumentException error) {
- logErrorMessage(error);
- }
- } else {
- // TODO: Fetch devices again.
+ if (intent == null || ACTION_UPDATE_NOTIFICATION.equals(intent.getAction())) {
+ return updateForegroundState() ? START_STICKY : START_NOT_STICKY;
}
-
- return updateForegroundState() ? START_STICKY : START_NOT_STICKY;
+ return START_NOT_STICKY;
}
/**
@@ -92,6 +70,7 @@
final int[] deviceIds = provider.getOpenedDeviceIds();
int notificationId = 0;
Notification notification = null;
+ // TODO: Hide notification if the device has already been removed.
for (final int deviceId : deviceIds) {
try {
final String title = getResources().getString(
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java
index efe5ff1..5519efd 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java
@@ -273,9 +273,7 @@
final MtpRoot[] results = new MtpRoot[storageIds.length];
for (int i = 0; i < storageIds.length; i++) {
results[i] = new MtpRoot(
- device.getDeviceId(),
- device.getDeviceInfo().getModel(),
- device.getStorageInfo(storageIds[i]));
+ device.getDeviceId(), device.getStorageInfo(storageIds[i]));
}
return results;
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpRoot.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpRoot.java
index ec338c3..8530aaf 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpRoot.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpRoot.java
@@ -16,7 +16,6 @@
package com.android.mtp;
-import android.content.res.Resources;
import android.mtp.MtpStorageInfo;
import com.android.internal.annotations.VisibleForTesting;
@@ -24,7 +23,6 @@
class MtpRoot {
final int mDeviceId;
final int mStorageId;
- final String mDeviceModelName;
final String mDescription;
final long mFreeSpace;
final long mMaxCapacity;
@@ -33,24 +31,21 @@
@VisibleForTesting
MtpRoot(int deviceId,
int storageId,
- String deviceName,
String description,
long freeSpace,
long maxCapacity,
String volumeIdentifier) {
mDeviceId = deviceId;
mStorageId = storageId;
- mDeviceModelName = deviceName;
mDescription = description;
mFreeSpace = freeSpace;
mMaxCapacity = maxCapacity;
mVolumeIdentifier = volumeIdentifier;
}
- MtpRoot(int deviceId, String deviceModelName, MtpStorageInfo storageInfo) {
+ MtpRoot(int deviceId, MtpStorageInfo storageInfo) {
mDeviceId = deviceId;
mStorageId = storageInfo.getStorageId();
- mDeviceModelName = deviceModelName;
mDescription = storageInfo.getDescription();
mFreeSpace = storageInfo.getFreeSpace();
mMaxCapacity = storageInfo.getMaxCapacity();
@@ -64,7 +59,6 @@
final MtpRoot other = (MtpRoot) object;
return mDeviceId == other.mDeviceId &&
mStorageId == other.mStorageId &&
- mDeviceModelName.equals(other.mDeviceModelName) &&
mDescription.equals(other.mDescription) &&
mFreeSpace == other.mFreeSpace &&
mMaxCapacity == other.mMaxCapacity &&
@@ -73,19 +67,12 @@
@Override
public int hashCode() {
- return mDeviceId ^ mStorageId ^ mDeviceModelName.hashCode() ^ mDescription.hashCode() ^
+ return mDeviceId ^ mStorageId ^ mDescription.hashCode() ^
((int) mFreeSpace) ^ ((int) mMaxCapacity) ^ mVolumeIdentifier.hashCode();
}
-
+
@Override
public String toString() {
- return "MtpRoot{Name: " + mDeviceModelName + " " + mDescription + "}";
- }
-
- String getRootName(Resources resources) {
- return String.format(
- resources.getString(R.string.root_name),
- mDeviceModelName,
- mDescription);
+ return "MtpRoot{Name: " + mDescription + "}";
}
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
index 15b8ef3..a4c3cf4 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
@@ -1,12 +1,28 @@
+/*
+ * 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.mtp;
import android.content.ContentResolver;
-import android.content.res.Resources;
import android.net.Uri;
import android.os.Process;
import android.provider.DocumentsContract;
import android.util.Log;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
@@ -35,7 +51,6 @@
private final static long AWAIT_TERMINATION_TIMEOUT = 2000;
final ContentResolver mResolver;
- final Resources mResources;
final MtpManager mManager;
final MtpDatabase mDatabase;
@@ -44,11 +59,9 @@
RootScanner(
ContentResolver resolver,
- Resources resources,
MtpManager manager,
MtpDatabase database) {
mResolver = resolver;
- mResources = resources;
mManager = manager;
mDatabase = database;
}
@@ -64,9 +77,8 @@
/**
* Starts to check new changes right away.
- * If the background thread has already gone, it restarts another background thread.
*/
- synchronized void resume() {
+ synchronized CountDownLatch resume() {
if (mExecutor == null) {
// Only single thread updates the database.
mExecutor = Executors.newSingleThreadExecutor();
@@ -75,8 +87,10 @@
// Cancel previous task.
mCurrentTask.cancel(true);
}
- mCurrentTask = new FutureTask<Void>(new UpdateRootsRunnable(), null);
+ final UpdateRootsRunnable runnable = new UpdateRootsRunnable();
+ mCurrentTask = new FutureTask<Void>(runnable, null);
mExecutor.submit(mCurrentTask);
+ return runnable.mFirstScanCompleted;
}
/**
@@ -98,6 +112,8 @@
* Runnable to scan roots and update the database information.
*/
private final class UpdateRootsRunnable implements Runnable {
+ final CountDownLatch mFirstScanCompleted = new CountDownLatch(1);
+
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
@@ -124,8 +140,7 @@
continue;
}
mDatabase.getMapper().startAddingDocuments(documentId);
- if (mDatabase.getMapper().putStorageDocuments(
- documentId, mResources, device.roots)) {
+ if (mDatabase.getMapper().putStorageDocuments(documentId, device.roots)) {
changed = true;
}
if (mDatabase.getMapper().stopAddingDocuments(documentId)) {
@@ -136,6 +151,7 @@
if (changed) {
notifyChange();
}
+ mFirstScanCompleted.countDown();
pollingCount++;
try {
// Use SHORT_POLLING_PERIOD for the first SHORT_POLLING_TIMES because it is
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/ServiceIntentSender.java b/packages/MtpDocumentsProvider/src/com/android/mtp/ServiceIntentSender.java
new file mode 100644
index 0000000..a1bb2c1
--- /dev/null
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/ServiceIntentSender.java
@@ -0,0 +1,38 @@
+/*
+ * 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.mtp;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * Sends intent to MtpDocumentsService.
+ */
+class ServiceIntentSender {
+ private Context mContext;
+
+ ServiceIntentSender(Context context) {
+ mContext = context;
+ }
+
+ void sendUpdateNotificationIntent() {
+ final Intent intent = new Intent(MtpDocumentsService.ACTION_UPDATE_NOTIFICATION);
+ intent.setComponent(new ComponentName(mContext, MtpDocumentsService.class));
+ mContext.startService(intent);
+ }
+}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/UsbIntentReceiver.java b/packages/MtpDocumentsProvider/src/com/android/mtp/UsbIntentReceiver.java
index 0ac130e..0489ea8 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/UsbIntentReceiver.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/UsbIntentReceiver.java
@@ -21,7 +21,9 @@
import android.content.Intent;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
-import android.net.Uri;
+import android.util.Log;
+
+import java.io.IOException;
public class UsbIntentReceiver extends BroadcastReceiver {
@Override
@@ -29,17 +31,15 @@
final UsbDevice device = intent.getExtras().getParcelable(UsbManager.EXTRA_DEVICE);
switch (intent.getAction()) {
case UsbManager.ACTION_USB_DEVICE_ATTACHED:
- startService(context, MtpDocumentsService.ACTION_OPEN_DEVICE, device);
+ MtpDocumentsProvider.getInstance().resumeRootScanner();
break;
case UsbManager.ACTION_USB_DEVICE_DETACHED:
- startService(context, MtpDocumentsService.ACTION_CLOSE_DEVICE, device);
+ try {
+ MtpDocumentsProvider.getInstance().closeDevice(device.getDeviceId());
+ } catch (IOException | InterruptedException e) {
+ Log.e(MtpDocumentsProvider.TAG, "Failed to close device", e);
+ }
break;
}
}
-
- private void startService(Context context, String action, UsbDevice device) {
- final Intent intent = new Intent(action, Uri.EMPTY, context, MtpDocumentsService.class);
- intent.putExtra(MtpDocumentsService.EXTRA_DEVICE, device);
- context.startService(intent);
- }
}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
index 5f71606..af1fed4 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
@@ -42,8 +42,8 @@
public void setUp() {
mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
mDatabase.getMapper().startAddingDocuments("deviceDocId");
- mDatabase.getMapper().putStorageDocuments("deviceDocId", new TestResources(), new MtpRoot[] {
- new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "")
+ mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
+ new MtpRoot(0, 0, "Storage", 1000, 1000, "")
});
mDatabase.getMapper().stopAddingDocuments("deviceDocId");
mManager = new BlockableTestMtpManager(getContext());
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
index 4022886..a49dc67 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
@@ -81,8 +81,8 @@
mDatabase.getMapper().stopAddingDocuments(null);
mDatabase.getMapper().startAddingDocuments("1");
- mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
- new MtpRoot(0, 1, "Device", "Storage", 1000, 2000, "")
+ mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
+ new MtpRoot(0, 1, "Storage", 1000, 2000, "")
});
mDatabase.getMapper().stopAddingDocuments("1");
@@ -96,7 +96,7 @@
assertEquals(1, getInt(cursor, COLUMN_STORAGE_ID));
assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE));
assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, getString(cursor, COLUMN_MIME_TYPE));
- assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME));
+ assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
assertTrue(isNull(cursor, COLUMN_SUMMARY));
assertTrue(isNull(cursor, COLUMN_LAST_MODIFIED));
assertEquals(R.drawable.ic_root_mtp, getInt(cursor, COLUMN_ICON));
@@ -109,7 +109,7 @@
}
{
- final Cursor cursor = mDatabase.queryRoots(new String [] {
+ final Cursor cursor = mDatabase.queryRoots(resources, new String [] {
Root.COLUMN_ROOT_ID,
Root.COLUMN_FLAGS,
Root.COLUMN_ICON,
@@ -139,10 +139,10 @@
public void testPutStorageDocuments() throws Exception {
mDatabase.getMapper().startAddingDocuments("deviceDocId");
- mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] {
- new MtpRoot(0, 1, "Device", "Storage", 1000, 2000, ""),
- new MtpRoot(0, 2, "Device", "Storage", 2000, 4000, ""),
- new MtpRoot(0, 3, "Device", "/@#%&<>Storage", 3000, 6000,"")
+ mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
+ new MtpRoot(0, 1, "Storage", 1000, 2000, ""),
+ new MtpRoot(0, 2, "Storage", 2000, 4000, ""),
+ new MtpRoot(0, 3, "/@#%&<>Storage", 3000, 6000,"")
});
{
@@ -155,7 +155,7 @@
assertEquals(1, getInt(cursor, COLUMN_STORAGE_ID));
assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE));
assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, getString(cursor, COLUMN_MIME_TYPE));
- assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME));
+ assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
assertTrue(isNull(cursor, COLUMN_SUMMARY));
assertTrue(isNull(cursor, COLUMN_LAST_MODIFIED));
assertEquals(R.drawable.ic_root_mtp, getInt(cursor, COLUMN_ICON));
@@ -166,11 +166,11 @@
cursor.moveToNext();
assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
- assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME));
+ assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.moveToNext();
assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
- assertEquals("Device /@#%&<>Storage", getString(cursor, COLUMN_DISPLAY_NAME));
+ assertEquals("/@#%&<>Storage", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.close();
}
@@ -264,9 +264,9 @@
};
mDatabase.getMapper().startAddingDocuments("deviceDocId");
- mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] {
- new MtpRoot(0, 100, "Device", "Storage A", 1000, 0, ""),
- new MtpRoot(0, 101, "Device", "Storage B", 1001, 0, "")
+ mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
+ new MtpRoot(0, 100, "Storage A", 1000, 0, ""),
+ new MtpRoot(0, 101, "Storage B", 1001, 0, "")
});
{
@@ -275,11 +275,11 @@
cursor.moveToNext();
assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals(100, getInt(cursor, COLUMN_STORAGE_ID));
- assertEquals("Device Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
+ assertEquals("Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.moveToNext();
assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals(101, getInt(cursor, COLUMN_STORAGE_ID));
- assertEquals("Device Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
+ assertEquals("Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.close();
}
@@ -291,18 +291,18 @@
cursor.moveToNext();
assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
assertTrue(isNull(cursor, COLUMN_STORAGE_ID));
- assertEquals("Device Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
+ assertEquals("Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.moveToNext();
assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
assertTrue(isNull(cursor, COLUMN_STORAGE_ID));
- assertEquals("Device Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
+ assertEquals("Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.close();
}
mDatabase.getMapper().startAddingDocuments("deviceDocId");
- mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] {
- new MtpRoot(0, 200, "Device", "Storage A", 2000, 0, ""),
- new MtpRoot(0, 202, "Device", "Storage C", 2002, 0, "")
+ mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
+ new MtpRoot(0, 200, "Storage A", 2000, 0, ""),
+ new MtpRoot(0, 202, "Storage C", 2002, 0, "")
});
{
@@ -311,15 +311,15 @@
cursor.moveToNext();
assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
assertTrue(isNull(cursor, COLUMN_STORAGE_ID));
- assertEquals("Device Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
+ assertEquals("Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.moveToNext();
assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
assertTrue(isNull(cursor, COLUMN_STORAGE_ID));
- assertEquals("Device Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
+ assertEquals("Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.moveToNext();
assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals(202, getInt(cursor, COLUMN_STORAGE_ID));
- assertEquals("Device Storage C", getString(cursor, COLUMN_DISPLAY_NAME));
+ assertEquals("Storage C", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.close();
}
@@ -331,11 +331,11 @@
cursor.moveToNext();
assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals(200, getInt(cursor, COLUMN_STORAGE_ID));
- assertEquals("Device Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
+ assertEquals("Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.moveToNext();
assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals(202, getInt(cursor, COLUMN_STORAGE_ID));
- assertEquals("Device Storage C", getString(cursor, COLUMN_DISPLAY_NAME));
+ assertEquals("Storage C", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.close();
}
}
@@ -432,11 +432,11 @@
mDatabase.getMapper().startAddingDocuments("1");
mDatabase.getMapper().startAddingDocuments("2");
- mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
- new MtpRoot(0, 100, "Device A", "Storage", 0, 0, "")
+ mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
+ new MtpRoot(0, 100, "Storage", 0, 0, "")
});
- mDatabase.getMapper().putStorageDocuments("2", resources, new MtpRoot[] {
- new MtpRoot(1, 100, "Device B", "Storage", 0, 0, "")
+ mDatabase.getMapper().putStorageDocuments("2", new MtpRoot[] {
+ new MtpRoot(1, 100, "Storage", 0, 0, "")
});
{
@@ -445,16 +445,16 @@
cursor.moveToNext();
assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals(100, getInt(cursor, COLUMN_STORAGE_ID));
- assertEquals("Device A Storage", getString(cursor, COLUMN_DISPLAY_NAME));
+ assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.moveToNext();
assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals(100, getInt(cursor, COLUMN_STORAGE_ID));
- assertEquals("Device B Storage", getString(cursor, COLUMN_DISPLAY_NAME));
+ assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.close();
}
{
- final Cursor cursor = mDatabase.queryRoots(rootColumns);
+ final Cursor cursor = mDatabase.queryRoots(resources, rootColumns);
assertEquals(2, cursor.getCount());
cursor.moveToNext();
assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID));
@@ -469,11 +469,11 @@
mDatabase.getMapper().startAddingDocuments("1");
mDatabase.getMapper().startAddingDocuments("2");
- mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
- new MtpRoot(0, 200, "Device", "Storage", 2000, 0, "")
+ mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
+ new MtpRoot(0, 200, "Storage", 2000, 0, "")
});
- mDatabase.getMapper().putStorageDocuments("2", resources, new MtpRoot[] {
- new MtpRoot(1, 300, "Device", "Storage", 3000, 0, "")
+ mDatabase.getMapper().putStorageDocuments("2", new MtpRoot[] {
+ new MtpRoot(1, 300, "Storage", 3000, 0, "")
});
mDatabase.getMapper().stopAddingDocuments("1");
mDatabase.getMapper().stopAddingDocuments("2");
@@ -482,18 +482,18 @@
final Cursor cursor = mDatabase.queryRootDocuments(columns);
assertEquals(2, cursor.getCount());
cursor.moveToNext();
- assertEquals(5, getInt(cursor, COLUMN_DOCUMENT_ID));
+ assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals(200, getInt(cursor, COLUMN_STORAGE_ID));
- assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME));
+ assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.moveToNext();
- assertEquals(6, getInt(cursor, COLUMN_DOCUMENT_ID));
+ assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals(300, getInt(cursor, COLUMN_STORAGE_ID));
- assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME));
+ assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.close();
}
{
- final Cursor cursor = mDatabase.queryRoots(rootColumns);
+ final Cursor cursor = mDatabase.queryRoots(resources, rootColumns);
assertEquals(2, cursor.getCount());
cursor.moveToNext();
assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID));
@@ -562,24 +562,24 @@
mDatabase.getMapper().startAddingDocuments(null);
mDatabase.getMapper().putDeviceDocument(
- new MtpDeviceRecord(0, "Device", false, new MtpRoot[0], null, null));
+ new MtpDeviceRecord(0, "Device", false, new MtpRoot[0], null, null));
mDatabase.getMapper().stopAddingDocuments(null);
mDatabase.getMapper().startAddingDocuments("1");
- mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
- new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""),
+ mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
+ new MtpRoot(0, 100, "Storage", 0, 0, ""),
});
mDatabase.getMapper().clearMapping();
mDatabase.getMapper().startAddingDocuments("1");
- mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
- new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""),
+ mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
+ new MtpRoot(0, 200, "Storage", 2000, 0, ""),
});
mDatabase.getMapper().clearMapping();
mDatabase.getMapper().startAddingDocuments("1");
- mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
- new MtpRoot(0, 300, "Device", "Storage", 3000, 0, ""),
+ mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
+ new MtpRoot(0, 300, "Storage", 3000, 0, ""),
});
mDatabase.getMapper().stopAddingDocuments("1");
@@ -589,11 +589,11 @@
cursor.moveToNext();
assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals(300, getInt(cursor, COLUMN_STORAGE_ID));
- assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME));
+ assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.close();
}
{
- final Cursor cursor = mDatabase.queryRoots(rootColumns);
+ final Cursor cursor = mDatabase.queryRoots(resources, rootColumns);
assertEquals(1, cursor.getCount());
cursor.moveToNext();
assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID));
@@ -610,15 +610,15 @@
};
mDatabase.getMapper().startAddingDocuments("deviceDocId");
- mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] {
- new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""),
+ mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
+ new MtpRoot(0, 100, "Storage", 0, 0, ""),
});
mDatabase.getMapper().clearMapping();
mDatabase.getMapper().startAddingDocuments("deviceDocId");
- mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] {
- new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""),
- new MtpRoot(0, 201, "Device", "Storage", 2001, 0, ""),
+ mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
+ new MtpRoot(0, 200, "Storage", 2000, 0, ""),
+ new MtpRoot(0, 201, "Storage", 2001, 0, ""),
});
mDatabase.getMapper().stopAddingDocuments("deviceDocId");
@@ -628,11 +628,11 @@
cursor.moveToNext();
assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals(200, getInt(cursor, COLUMN_STORAGE_ID));
- assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME));
+ assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.moveToNext();
assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals(201, getInt(cursor, COLUMN_STORAGE_ID));
- assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME));
+ assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.close();
}
}
@@ -646,14 +646,14 @@
// The client code should be able to replace existing rows with new information.
// Add one.
mDatabase.getMapper().startAddingDocuments("1");
- mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
- new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
+ mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
+ new MtpRoot(0, 100, "Storage A", 0, 0, ""),
});
mDatabase.getMapper().stopAddingDocuments("1");
// Replace it.
mDatabase.getMapper().startAddingDocuments("1");
- mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
- new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""),
+ mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
+ new MtpRoot(0, 100, "Storage B", 1000, 1000, ""),
});
mDatabase.getMapper().stopAddingDocuments("1");
{
@@ -667,7 +667,7 @@
cursor.moveToNext();
assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals(100, getInt(cursor, COLUMN_STORAGE_ID));
- assertEquals("Device Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
+ assertEquals("Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.close();
}
{
@@ -676,7 +676,7 @@
Root.COLUMN_TITLE,
Root.COLUMN_AVAILABLE_BYTES
};
- final Cursor cursor = mDatabase.queryRoots(columns);
+ final Cursor cursor = mDatabase.queryRoots(resources, columns);
assertEquals(1, cursor.getCount());
cursor.moveToNext();
assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID));
@@ -695,21 +695,21 @@
mDatabase.getMapper().stopAddingDocuments(null);
mDatabase.getMapper().startAddingDocuments("1");
- mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
- new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
+ mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
+ new MtpRoot(0, 100, "Storage A", 0, 0, ""),
});
mDatabase.getMapper().clearMapping();
- final Cursor oldCursor = mDatabase.queryRoots(strings(Root.COLUMN_ROOT_ID));
+ final Cursor oldCursor = mDatabase.queryRoots(resources, strings(Root.COLUMN_ROOT_ID));
assertEquals(1, oldCursor.getCount());
// Add one.
mDatabase.getMapper().startAddingDocuments("1");
- mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
- new MtpRoot(0, 101, "Device", "Storage B", 1000, 1000, ""),
+ mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
+ new MtpRoot(0, 101, "Storage B", 1000, 1000, ""),
});
// Add one more before resolving unmapped documents.
- mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
- new MtpRoot(0, 102, "Device", "Storage B", 1000, 1000, ""),
+ mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
+ new MtpRoot(0, 102, "Storage B", 1000, 1000, ""),
});
mDatabase.getMapper().stopAddingDocuments("1");
@@ -729,15 +729,15 @@
public void testQueryDocuments() {
mDatabase.getMapper().startAddingDocuments("deviceDocId");
- mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] {
- new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
+ mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
+ new MtpRoot(0, 100, "Storage A", 0, 0, ""),
});
mDatabase.getMapper().stopAddingDocuments("deviceDocId");
final Cursor cursor = mDatabase.queryDocument("1", strings(Document.COLUMN_DISPLAY_NAME));
assertEquals(1, cursor.getCount());
cursor.moveToNext();
- assertEquals("Device Storage A", getString(cursor, Document.COLUMN_DISPLAY_NAME));
+ assertEquals("Storage A", getString(cursor, Document.COLUMN_DISPLAY_NAME));
cursor.close();
}
@@ -750,7 +750,7 @@
// It the device does not have storages, it shows a device root.
{
- final Cursor cursor = mDatabase.queryRoots(strings(Root.COLUMN_TITLE));
+ final Cursor cursor = mDatabase.queryRoots(resources, strings(Root.COLUMN_TITLE));
assertEquals(1, cursor.getCount());
cursor.moveToNext();
assertEquals("Device", cursor.getString(0));
@@ -758,14 +758,14 @@
}
mDatabase.getMapper().startAddingDocuments("1");
- mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
- new MtpRoot(0, 100, "Device", "Storage A", 0, 0, "")
+ mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
+ new MtpRoot(0, 100, "Storage A", 0, 0, "")
});
mDatabase.getMapper().stopAddingDocuments("1");
// It the device has single storage, it shows a storage root.
{
- final Cursor cursor = mDatabase.queryRoots(strings(Root.COLUMN_TITLE));
+ final Cursor cursor = mDatabase.queryRoots(resources, strings(Root.COLUMN_TITLE));
assertEquals(1, cursor.getCount());
cursor.moveToNext();
assertEquals("Device Storage A", cursor.getString(0));
@@ -773,15 +773,15 @@
}
mDatabase.getMapper().startAddingDocuments("1");
- mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
- new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
- new MtpRoot(0, 101, "Device", "Storage B", 0, 0, "")
+ mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
+ new MtpRoot(0, 100, "Storage A", 0, 0, ""),
+ new MtpRoot(0, 101, "Storage B", 0, 0, "")
});
mDatabase.getMapper().stopAddingDocuments("1");
// It the device has multiple storages, it shows a device root.
{
- final Cursor cursor = mDatabase.queryRoots(strings(Root.COLUMN_TITLE));
+ final Cursor cursor = mDatabase.queryRoots(resources, strings(Root.COLUMN_TITLE));
assertEquals(1, cursor.getCount());
cursor.moveToNext();
assertEquals("Device", cursor.getString(0));
@@ -791,8 +791,8 @@
public void testGetParentId() throws FileNotFoundException {
mDatabase.getMapper().startAddingDocuments("deviceDocId");
- mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] {
- new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
+ mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
+ new MtpRoot(0, 100, "Storage A", 0, 0, ""),
});
mDatabase.getMapper().stopAddingDocuments("deviceDocId");
@@ -810,8 +810,8 @@
public void testDeleteDocument() {
mDatabase.getMapper().startAddingDocuments("deviceDocId");
- mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] {
- new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
+ mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
+ new MtpRoot(0, 100, "Storage A", 0, 0, ""),
});
mDatabase.getMapper().stopAddingDocuments("deviceDocId");
@@ -854,8 +854,8 @@
public void testPutNewDocument() {
mDatabase.getMapper().startAddingDocuments("deviceDocId");
- mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] {
- new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
+ mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
+ new MtpRoot(0, 100, "Storage A", 0, 0, ""),
});
mDatabase.getMapper().stopAddingDocuments("deviceDocId");
@@ -911,7 +911,7 @@
DocumentsContract.Root.COLUMN_TITLE,
DocumentsContract.Root.COLUMN_AVAILABLE_BYTES
};
- try (final Cursor cursor = mDatabase.queryRoots(columns)) {
+ try (final Cursor cursor = mDatabase.queryRoots(resources, columns)) {
assertEquals(1, cursor.getCount());
assertTrue(cursor.moveToNext());
assertEquals(1, cursor.getLong(0));
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index 3606612..5b0f557 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -23,13 +23,11 @@
import android.os.ParcelFileDescriptor;
import android.os.storage.StorageManager;
import android.provider.DocumentsContract.Root;
-import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.provider.DocumentsContract;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.MediumTest;
-import android.util.Log;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -64,13 +62,12 @@
setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
mMtpManager.addValidDevice(new MtpDeviceRecord(
0,
- "Device",
+ "Device A",
false /* unopened */,
new MtpRoot[] {
new MtpRoot(
0 /* deviceId */,
1 /* storageId */,
- "Device A" /* device model name */,
"Storage A" /* volume description */,
1024 /* free space */,
2048 /* total space */,
@@ -79,11 +76,14 @@
null,
null));
- mProvider.openDevice(0);
+ mProvider.resumeRootScanner();
mResolver.waitForNotification(ROOTS_URI, 1);
- mProvider.closeDevice(0);
+ mProvider.openDevice(0);
mResolver.waitForNotification(ROOTS_URI, 2);
+
+ mProvider.closeDevice(0);
+ mResolver.waitForNotification(ROOTS_URI, 3);
}
public void testOpenAndCloseErrorDevice() throws Exception {
@@ -94,24 +94,17 @@
} catch (Throwable error) {
assertTrue(error instanceof IOException);
}
-
- try {
- mProvider.closeDevice(1);
- fail();
- } catch (Throwable error) {
- assertTrue(error instanceof IOException);
- }
+ assertEquals(0, mProvider.getOpenedDeviceIds().length);
// Check if the following notification is the first one or not.
mMtpManager.addValidDevice(new MtpDeviceRecord(
0,
- "Device",
+ "Device A",
false /* unopened */,
new MtpRoot[] {
new MtpRoot(
0 /* deviceId */,
1 /* storageId */,
- "Device A" /* device model name */,
"Storage A" /* volume description */,
1024 /* free space */,
2048 /* total space */,
@@ -119,21 +112,66 @@
},
null,
null));
- mProvider.openDevice(0);
+ mProvider.resumeRootScanner();
mResolver.waitForNotification(ROOTS_URI, 1);
+ mProvider.openDevice(0);
+ mResolver.waitForNotification(ROOTS_URI, 2);
+ }
+
+ public void testOpenDeviceOnDemand() throws Exception {
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
+ mMtpManager.addValidDevice(new MtpDeviceRecord(
+ 0,
+ "Device A",
+ false /* unopened */,
+ new MtpRoot[] {
+ new MtpRoot(
+ 0 /* deviceId */,
+ 1 /* storageId */,
+ "Storage A" /* volume description */,
+ 1024 /* free space */,
+ 2048 /* total space */,
+ "" /* no volume identifier */)
+ },
+ null,
+ null));
+ mMtpManager.setObjectHandles(0, 1, -1, new int[0]);
+ mProvider.resumeRootScanner();
+ mResolver.waitForNotification(ROOTS_URI, 1);
+ final String[] columns = new String[] {
+ DocumentsContract.Root.COLUMN_TITLE,
+ DocumentsContract.Root.COLUMN_DOCUMENT_ID
+ };
+ try (final Cursor cursor = mProvider.queryRoots(columns)) {
+ assertEquals(1, cursor.getCount());
+ assertTrue(cursor.moveToNext());
+ assertEquals("Device A", cursor.getString(0));
+ assertEquals(1, cursor.getLong(1));
+ }
+ {
+ final int [] openedDevice = mProvider.getOpenedDeviceIds();
+ assertEquals(0, openedDevice.length);
+ }
+ // Device is opened automatically when querying its children.
+ try (final Cursor cursor = mProvider.queryChildDocuments("1", null, null)) {}
+
+ {
+ final int [] openedDevice = mProvider.getOpenedDeviceIds();
+ assertEquals(1, openedDevice.length);
+ assertEquals(0, openedDevice[0]);
+ }
}
public void testQueryRoots() throws Exception {
setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
mMtpManager.addValidDevice(new MtpDeviceRecord(
0,
- "Device",
+ "Device A",
false /* unopened */,
new MtpRoot[] {
new MtpRoot(
0 /* deviceId */,
1 /* storageId */,
- "Device A" /* device model name */,
"Storage A" /* volume description */,
1024 /* free space */,
2048 /* total space */,
@@ -143,13 +181,12 @@
null));
mMtpManager.addValidDevice(new MtpDeviceRecord(
1,
- "Device",
+ "Device B",
false /* unopened */,
new MtpRoot[] {
new MtpRoot(
1 /* deviceId */,
1 /* storageId */,
- "Device B" /* device model name */,
"Storage B" /* volume description */,
2048 /* free space */,
4096 /* total space */,
@@ -194,13 +231,12 @@
0, "Device A", false /* unopened */, new MtpRoot[0], null, null));
mMtpManager.addValidDevice(new MtpDeviceRecord(
1,
- "Device",
+ "Device B",
false /* unopened */,
new MtpRoot[] {
new MtpRoot(
1 /* deviceId */,
1 /* storageId */,
- "Device B" /* device model name */,
"Storage B" /* volume description */,
2048 /* free space */,
4096 /* total space */,
@@ -210,9 +246,13 @@
null));
{
mProvider.openDevice(0);
- mProvider.openDevice(1);
+ mProvider.resumeRootScanner();
mResolver.waitForNotification(ROOTS_URI, 1);
+ mProvider.openDevice(1);
+ mProvider.resumeRootScanner();
+ mResolver.waitForNotification(ROOTS_URI, 2);
+
final Cursor cursor = mProvider.queryRoots(null);
assertEquals(2, cursor.getCount());
@@ -236,7 +276,7 @@
public void testQueryDocument() throws IOException, InterruptedException, TimeoutException {
setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
- setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "") });
+ setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
setupDocuments(
0,
0,
@@ -273,7 +313,7 @@
public void testQueryDocument_directory()
throws IOException, InterruptedException, TimeoutException {
setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
- setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "") });
+ setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
setupDocuments(
0,
0,
@@ -312,7 +352,6 @@
new MtpRoot(
0 /* deviceId */,
1 /* storageId */,
- "Device A" /* device model name */,
"Storage A" /* volume description */,
1024 /* free space */,
4096 /* total space */,
@@ -324,7 +363,7 @@
cursor.moveToNext();
assertEquals("2", cursor.getString(0));
assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1));
- assertEquals("Device A Storage A", cursor.getString(2));
+ assertEquals("Storage A", cursor.getString(2));
assertTrue(cursor.isNull(3));
assertEquals(0, cursor.getInt(4));
assertEquals(3072, cursor.getInt(5));
@@ -332,7 +371,7 @@
public void testQueryChildDocuments() throws Exception {
setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
- setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "") });
+ setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
setupDocuments(
0,
0,
@@ -375,7 +414,7 @@
public void testQueryChildDocuments_documentError() throws Exception {
setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
- setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "") });
+ 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);
@@ -388,7 +427,7 @@
public void testDeleteDocument() throws IOException, InterruptedException, TimeoutException {
setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
setupRoots(0, new MtpRoot[] {
- new MtpRoot(0, 0, "Device", "Storage", 0, 0, "")
+ new MtpRoot(0, 0, "Storage", 0, 0, "")
});
setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
new MtpObjectInfo.Builder()
@@ -408,7 +447,7 @@
throws IOException, InterruptedException, TimeoutException {
setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
setupRoots(0, new MtpRoot[] {
- new MtpRoot(0, 0, "Device", "Storage", 0, 0, "")
+ new MtpRoot(0, 0, "Storage", 0, 0, "")
});
setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
new MtpObjectInfo.Builder()
@@ -431,7 +470,7 @@
public void testOpenDocument() throws Exception {
setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
setupRoots(0, new MtpRoot[] {
- new MtpRoot(0, 0, "Device", "Storage", 0, 0, "")
+ new MtpRoot(0, 0, "Storage", 0, 0, "")
});
final byte[] bytes = "Hello world".getBytes();
setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
@@ -469,7 +508,7 @@
};
setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
setupRoots(0, new MtpRoot[] {
- new MtpRoot(0, 0, "Device", "Storage", 0, 0, "")
+ new MtpRoot(0, 0, "Storage", 0, 0, "")
});
final byte[] bytes = "Hello world".getBytes();
setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
@@ -492,7 +531,12 @@
mProvider = new MtpDocumentsProvider();
final StorageManager storageManager = getContext().getSystemService(StorageManager.class);
assertTrue(mProvider.onCreateForTesting(
- mResources, mMtpManager, mResolver, mDatabase, storageManager));
+ mResources,
+ mMtpManager,
+ mResolver,
+ mDatabase,
+ storageManager,
+ new TestServiceIntentSender()));
}
private String[] getStrings(Cursor cursor) {
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResources.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResources.java
index eb80e3b..b23038b 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResources.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResources.java
@@ -27,4 +27,9 @@
}
throw new NotFoundException();
}
+
+ @Override
+ public String getString(int id, Object... formatArgs) throws NotFoundException {
+ return String.format(getString(id), formatArgs);
+ }
}
diff --git a/wifi/java/android/net/wifi/ScanInfo.aidl b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestServiceIntentSender.java
similarity index 61%
copy from wifi/java/android/net/wifi/ScanInfo.aidl
copy to packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestServiceIntentSender.java
index 18ae508..d4a4a48 100644
--- a/wifi/java/android/net/wifi/ScanInfo.aidl
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestServiceIntentSender.java
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2015, The Android Open Source Project
+/*
+ * Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,6 +14,13 @@
* limitations under the License.
*/
-package android.net.wifi;
+package com.android.mtp;
-parcelable ScanInfo;
+class TestServiceIntentSender extends ServiceIntentSender {
+ TestServiceIntentSender() {
+ super(null);
+ }
+
+ @Override
+ void sendUpdateNotificationIntent() {}
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index a1ea658..d0cf635 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -265,7 +265,14 @@
new Runnable() {
@Override
public void run() {
- onConnectedToPrintSpooler(adapter);
+ if (isFinishing()) {
+ // onPause might have not been able to cancel the job, see PrintActivity#onPause
+ // To be sure, cancel the job again. Double canceling does no harm.
+ mSpoolerProvider.getSpooler().setPrintJobState(mPrintJob.getId(),
+ PrintJobInfo.STATE_CANCELED, null);
+ } else {
+ onConnectedToPrintSpooler(adapter);
+ }
}
});
}
@@ -353,7 +360,9 @@
if (mState == STATE_INITIALIZING) {
if (isFinishing()) {
- spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_CANCELED, null);
+ if (spooler != null) {
+ spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_CANCELED, null);
+ }
}
super.onPause();
return;
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index d3c8416..38d8907 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -515,9 +515,9 @@
<string name="pointer_location_summary">Screen overlay showing current touch data</string>
<!-- UI debug setting: show touches? [CHAR LIMIT=25] -->
- <string name="show_touches">Show touches</string>
+ <string name="show_touches">Show taps</string>
<!-- UI debug setting: show touches location summary [CHAR LIMIT=50] -->
- <string name="show_touches_summary">Show visual feedback for touches</string>
+ <string name="show_touches_summary">Show visual feedback for taps</string>
<!-- UI debug setting: show where surface updates happen? [CHAR LIMIT=25] -->
<string name="show_screen_updates">Show surface updates</string>
@@ -634,7 +634,7 @@
<!-- Summary text of the "local backup password" setting when the user has not supplied a password -->
<string name="local_backup_password_summary_none">Desktop full backups aren\u2019t currently protected</string>
<!-- Summary text of the "local backup password" setting when the user has already supplied a password -->
- <string name="local_backup_password_summary_change">Touch to change or remove the password for desktop full backups</string>
+ <string name="local_backup_password_summary_change">Tap to change or remove the password for desktop full backups</string>
<!-- Toast message shown when the user successfully sets a new local backup password [CHAR LIMIT=80] -->
<string name="local_backup_password_toast_success">New backup password set</string>
@@ -660,9 +660,9 @@
<!-- Settings item title for inactive apps [CHAR LIMIT=35] -->
<string name="inactive_apps_title">Inactive apps</string>
<!-- Settings item summary for inactive app [CHAR LIMIT=100] -->
- <string name="inactive_app_inactive_summary">Inactive. Touch to toggle.</string>
+ <string name="inactive_app_inactive_summary">Inactive. Tap to toggle.</string>
<!-- Settings item summary for active app [CHAR LIMIT=100] -->
- <string name="inactive_app_active_summary">Active. Touch to toggle.</string>
+ <string name="inactive_app_active_summary">Active. Tap to toggle.</string>
<!-- Services settings screen, setting option name for the user to go to the screen to view running services -->
<string name="runningservices_settings_title">Running services</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java
index 78c530c..f885110 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java
@@ -155,7 +155,7 @@
for (UserInfo userInfo : um.getProfiles(userId)) {
final List<ComponentName> admins = dpm.getActiveAdminsAsUser(userInfo.id);
if (admins == null) {
- return null;
+ continue;
}
final boolean isSeparateProfileChallengeEnabled =
lockPatternUtils.isSeparateProfileChallengeEnabled(userInfo.id);
@@ -209,16 +209,7 @@
IPackageManager ipm = AppGlobals.getPackageManager();
try {
if (ipm.getBlockUninstallForUser(packageName, userId)) {
- DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
- Context.DEVICE_POLICY_SERVICE);
- if (dpm == null) {
- return null;
- }
- ComponentName admin = dpm.getProfileOwner();
- if (admin == null) {
- admin = dpm.getDeviceOwnerComponentOnCallingUser();
- }
- return new EnforcedAdmin(admin, UserHandle.myUserId());
+ return getProfileOrDeviceOwner(context, userId);
}
} catch (RemoteException e) {
// Nothing to do
@@ -238,7 +229,7 @@
try {
ApplicationInfo ai = ipm.getApplicationInfo(packageName, 0, userId);
if (ai != null && ((ai.flags & ApplicationInfo.FLAG_SUSPENDED) != 0)) {
- return getProfileOrDeviceOwnerOnCallingUser(context);
+ return getProfileOrDeviceOwner(context, userId);
}
} catch (RemoteException e) {
// Nothing to do
@@ -246,6 +237,80 @@
return null;
}
+ public static EnforcedAdmin checkIfInputMethodDisallowed(Context context,
+ String packageName, int userId) {
+ DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
+ Context.DEVICE_POLICY_SERVICE);
+ if (dpm == null) {
+ return null;
+ }
+ EnforcedAdmin admin = getProfileOrDeviceOwner(context, userId);
+ boolean permitted = true;
+ if (admin != null) {
+ permitted = dpm.isInputMethodPermittedByAdmin(admin.component,
+ packageName, userId);
+ }
+ int managedProfileId = getManagedProfileId(context, userId);
+ EnforcedAdmin profileAdmin = getProfileOrDeviceOwner(context, managedProfileId);
+ boolean permittedByProfileAdmin = true;
+ if (profileAdmin != null) {
+ permittedByProfileAdmin = dpm.isInputMethodPermittedByAdmin(profileAdmin.component,
+ packageName, managedProfileId);
+ }
+ if (!permitted && !permittedByProfileAdmin) {
+ return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
+ } else if (!permitted) {
+ return admin;
+ } else if (!permittedByProfileAdmin) {
+ return profileAdmin;
+ }
+ return null;
+ }
+
+ public static EnforcedAdmin checkIfAccessibilityServiceDisallowed(Context context,
+ String packageName, int userId) {
+ DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
+ Context.DEVICE_POLICY_SERVICE);
+ if (dpm == null) {
+ return null;
+ }
+ EnforcedAdmin admin = getProfileOrDeviceOwner(context, userId);
+ boolean permitted = true;
+ if (admin != null) {
+ permitted = dpm.isAccessibilityServicePermittedByAdmin(admin.component,
+ packageName, userId);
+ }
+ int managedProfileId = getManagedProfileId(context, userId);
+ EnforcedAdmin profileAdmin = getProfileOrDeviceOwner(context, managedProfileId);
+ boolean permittedByProfileAdmin = true;
+ if (profileAdmin != null) {
+ permittedByProfileAdmin = dpm.isAccessibilityServicePermittedByAdmin(
+ profileAdmin.component, packageName, managedProfileId);
+ }
+ if (!permitted && !permittedByProfileAdmin) {
+ return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
+ } else if (!permitted) {
+ return admin;
+ } else if (!permittedByProfileAdmin) {
+ return profileAdmin;
+ }
+ return null;
+ }
+
+ private static int getManagedProfileId(Context context, int userId) {
+ UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ List<UserInfo> userProfiles = um.getProfiles(userId);
+ for (UserInfo uInfo : userProfiles) {
+ if (uInfo.id == userId) {
+ continue;
+ }
+ if (uInfo.isManagedProfile()) {
+ return uInfo.id;
+ }
+ }
+ return UserHandle.USER_NULL;
+ }
+
/**
* Check if account management for a specific type of account is disabled by admin.
* Only a profile or device owner can disable account management. So, we check if account
@@ -255,7 +320,7 @@
* or {@code null} if the account management is not disabled.
*/
public static EnforcedAdmin checkIfAccountManagementDisabled(Context context,
- String accountType) {
+ String accountType, int userId) {
if (accountType == null) {
return null;
}
@@ -265,7 +330,7 @@
return null;
}
boolean isAccountTypeDisabled = false;
- String[] disabledTypes = dpm.getAccountTypesWithManagementDisabled();
+ String[] disabledTypes = dpm.getAccountTypesWithManagementDisabledAsUser(userId);
for (String type : disabledTypes) {
if (accountType.equals(type)) {
isAccountTypeDisabled = true;
@@ -275,7 +340,7 @@
if (!isAccountTypeDisabled) {
return null;
}
- return getProfileOrDeviceOwnerOnCallingUser(context);
+ return getProfileOrDeviceOwner(context, userId);
}
/**
@@ -296,7 +361,7 @@
}
/**
- * Checks if an admin has enforced minimum password quality requirements on the device.
+ * Checks if an admin has enforced minimum password quality requirements on the given user.
*
* @return EnforcedAdmin Object containing the enforced admin component and admin user details,
* or {@code null} if no quality requirements are set. If the requirements are set by
@@ -304,35 +369,73 @@
* {@link UserHandle#USER_NULL}.
*
*/
- public static EnforcedAdmin checkIfPasswordQualityIsSet(Context context) {
+ public static EnforcedAdmin checkIfPasswordQualityIsSet(Context context, int userId) {
final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
Context.DEVICE_POLICY_SERVICE);
if (dpm == null) {
return null;
}
- boolean isDisabledByMultipleAdmins = false;
- ComponentName adminComponent = null;
- List<ComponentName> admins = dpm.getActiveAdmins();
- int quality;
- if (admins != null) {
+
+ LockPatternUtils lockPatternUtils = new LockPatternUtils(context);
+ EnforcedAdmin enforcedAdmin = null;
+ if (lockPatternUtils.isSeparateProfileChallengeEnabled(userId)) {
+ // userId is managed profile and has a separate challenge, only consider
+ // the admins in that user.
+ final List<ComponentName> admins = dpm.getActiveAdminsAsUser(userId);
+ if (admins == null) {
+ return null;
+ }
for (ComponentName admin : admins) {
- quality = dpm.getPasswordQuality(admin);
- if (quality >= DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
- if (adminComponent == null) {
- adminComponent = admin;
+ if (dpm.getPasswordQuality(admin, userId)
+ > DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
+ if (enforcedAdmin == null) {
+ enforcedAdmin = new EnforcedAdmin(admin, userId);
} else {
- isDisabledByMultipleAdmins = true;
- break;
+ return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
}
}
}
- }
- EnforcedAdmin enforcedAdmin = null;
- if (adminComponent != null) {
- if (!isDisabledByMultipleAdmins) {
- enforcedAdmin = new EnforcedAdmin(adminComponent, UserHandle.myUserId());
- } else {
- enforcedAdmin = new EnforcedAdmin();
+ } else {
+ // Return all admins for this user and the profiles that are visible from this
+ // user that do not use a separate work challenge.
+ final UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ for (UserInfo userInfo : um.getProfiles(userId)) {
+ final List<ComponentName> admins = dpm.getActiveAdminsAsUser(userInfo.id);
+ if (admins == null) {
+ continue;
+ }
+ final boolean isSeparateProfileChallengeEnabled =
+ lockPatternUtils.isSeparateProfileChallengeEnabled(userInfo.id);
+ for (ComponentName admin : admins) {
+ if (!isSeparateProfileChallengeEnabled) {
+ if (dpm.getPasswordQuality(admin, userInfo.id)
+ > DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
+ if (enforcedAdmin == null) {
+ enforcedAdmin = new EnforcedAdmin(admin, userInfo.id);
+ } else {
+ return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
+ }
+ // This same admins could have set policies both on the managed profile
+ // and on the parent. So, if the admin has set the policy on the
+ // managed profile here, we don't need to further check if that admin
+ // has set policy on the parent admin.
+ continue;
+ }
+ }
+ if (userInfo.isManagedProfile()) {
+ // If userInfo.id is a managed profile, we also need to look at
+ // the policies set on the parent.
+ DevicePolicyManager parentDpm = dpm.getParentProfileInstance(userInfo);
+ if (parentDpm.getPasswordQuality(admin, userInfo.id)
+ > DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
+ if (enforcedAdmin == null) {
+ enforcedAdmin = new EnforcedAdmin(admin, userInfo.id);
+ } else {
+ return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
+ }
+ }
+ }
+ }
}
}
return enforcedAdmin;
@@ -352,7 +455,8 @@
EnforcedAdmin enforcedAdmin = null;
final int userId = UserHandle.myUserId();
if (lockPatternUtils.isSeparateProfileChallengeEnabled(userId)) {
- // If the user has a separate challenge, only consider the admins in that user.
+ // userId is managed profile and has a separate challenge, only consider
+ // the admins in that user.
final List<ComponentName> admins = dpm.getActiveAdminsAsUser(userId);
if (admins == null) {
return null;
@@ -373,7 +477,7 @@
for (UserInfo userInfo : um.getProfiles(userId)) {
final List<ComponentName> admins = dpm.getActiveAdminsAsUser(userInfo.id);
if (admins == null) {
- return null;
+ continue;
}
final boolean isSeparateProfileChallengeEnabled =
lockPatternUtils.isSeparateProfileChallengeEnabled(userInfo.id);
@@ -410,19 +514,24 @@
return enforcedAdmin;
}
- public static EnforcedAdmin getProfileOrDeviceOwnerOnCallingUser(Context context) {
+ public static EnforcedAdmin getProfileOrDeviceOwner(Context context, int userId) {
+ if (userId == UserHandle.USER_NULL) {
+ return null;
+ }
final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
Context.DEVICE_POLICY_SERVICE);
if (dpm == null) {
return null;
}
- ComponentName adminComponent = dpm.getDeviceOwnerComponentOnCallingUser();
+ ComponentName adminComponent = dpm.getProfileOwnerAsUser(userId);
if (adminComponent != null) {
- return new EnforcedAdmin(adminComponent, UserHandle.myUserId());
+ return new EnforcedAdmin(adminComponent, userId);
}
- adminComponent = dpm.getProfileOwner();
- if (adminComponent != null) {
- return new EnforcedAdmin(adminComponent, UserHandle.myUserId());
+ if (dpm.getDeviceOwnerUserId() == userId) {
+ adminComponent = dpm.getDeviceOwnerComponentOnAnyUser();
+ if (adminComponent != null) {
+ return new EnforcedAdmin(adminComponent, userId);
+ }
}
return null;
}
diff --git a/packages/SystemUI/res/layout/qs_add_tile_layout.xml b/packages/SystemUI/res/layout/qs_add_tile_layout.xml
deleted file mode 100644
index 962b00e..0000000
--- a/packages/SystemUI/res/layout/qs_add_tile_layout.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
--->
-
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:paddingTop="20dp"
- android:paddingStart="7dp"
- android:paddingEnd="7dp">
- <LinearLayout
- android:layout_height="wrap_content"
- android:layout_width="80dp"
- android:orientation="vertical">
- <ImageView
- android:id="@+id/tile_icon"
- android:layout_gravity="center"
- android:layout_width="@dimen/qs_tile_icon_size"
- android:layout_height="@dimen/qs_tile_icon_size" />
- <TextView
- android:id="@+id/tile_label"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center|bottom"
- android:paddingTop="10dp"
- android:gravity="center"
- android:textSize="@dimen/qs_tile_text_size" />
- </LinearLayout>
-</FrameLayout>
diff --git a/packages/SystemUI/res/layout/qs_customize_layout.xml b/packages/SystemUI/res/layout/qs_customize_divider.xml
similarity index 63%
rename from packages/SystemUI/res/layout/qs_customize_layout.xml
rename to packages/SystemUI/res/layout/qs_customize_divider.xml
index 0b8e02f..71ad85b 100644
--- a/packages/SystemUI/res/layout/qs_customize_layout.xml
+++ b/packages/SystemUI/res/layout/qs_customize_divider.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (C) 2015 The Android Open Source Project
+ Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,18 +14,16 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.systemui.qs.customize.NonPagedTileLayout
+
+<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/tiles_container"
+ android:id="@android:id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <view
- class="com.android.systemui.qs.PagedTileLayout$TilePage"
- android:id="@+id/tile_page"
- android:layout_width="match_parent"
- android:layout_height="wrap_content" />
-
-</com.android.systemui.qs.customize.NonPagedTileLayout>
-
+ android:paddingTop="20dp"
+ android:paddingStart="16dp"
+ android:paddingEnd="8dp"
+ android:paddingBottom="13dp"
+ android:textAppearance="@android:style/TextAppearance.Material.Body2"
+ android:textColor="?android:attr/colorAccent"
+ android:text="@string/drag_to_add_tiles" />
diff --git a/packages/SystemUI/res/layout/qs_customize_panel.xml b/packages/SystemUI/res/layout/qs_customize_panel.xml
index e56431b..73a92d9 100644
--- a/packages/SystemUI/res/layout/qs_customize_panel.xml
+++ b/packages/SystemUI/res/layout/qs_customize_panel.xml
@@ -22,85 +22,53 @@
android:background="@drawable/qs_customizer_background"
android:gravity="center_horizontal">
- <FrameLayout
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="@drawable/notification_header_bg">
+ android:paddingTop="28dp"
+ android:paddingEnd="8dp">
- <LinearLayout
- android:id="@+id/drag_buttons"
- android:layout_width="match_parent"
- android:layout_height="fill_parent"
- android:orientation="horizontal">
- <FrameLayout
- android:layout_width="0dp"
- android:layout_height="fill_parent"
- android:layout_weight="1">
- <com.android.systemui.qs.customize.DropButton
- android:id="@+id/info_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:gravity="center"
- android:drawableStart="@drawable/ic_info"
- android:drawablePadding="10dp"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="@android:color/white"
- android:text="@string/qs_customize_info" />
- </FrameLayout>
- <FrameLayout
- android:layout_width="0dp"
- android:layout_height="fill_parent"
- android:layout_weight="1">
- <com.android.systemui.qs.customize.DropButton
- android:id="@+id/remove_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:gravity="center"
- android:drawableStart="@drawable/ic_close_white"
- android:drawablePadding="10dp"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="@android:color/white"
- android:text="@string/qs_customize_remove" />
- </FrameLayout>
- </LinearLayout>
+ <ImageView
+ android:id="@+id/close"
+ android:layout_width="56dp"
+ android:layout_height="56dp"
+ android:padding="16dp"
+ android:src="@drawable/ic_close_white" />
- <Toolbar
- android:id="@*android:id/action_bar"
- android:layout_width="match_parent"
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_weight="1" />
+
+ <Button
+ android:id="@+id/save"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:navigationContentDescription="@*android:string/action_bar_up_description"
- android:background="@drawable/notification_header_bg"
- style="?android:attr/toolbarStyle" />
- </FrameLayout>
+ android:paddingStart="12dp"
+ android:paddingEnd="12dp"
+ android:background="?android:attr/selectableItemBackground"
+ android:textAppearance="@android:style/TextAppearance.Material.Widget.Button.Inverse"
+ android:textColor="?android:attr/colorAccent"
+ android:text="@string/save" />
- <com.android.systemui.tuner.AutoScrollView
+ <Button
+ android:id="@+id/reset"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingStart="12dp"
+ android:paddingEnd="12dp"
+ android:background="?android:attr/selectableItemBackground"
+ android:textAppearance="@android:style/TextAppearance.Material.Widget.Button.Inverse"
+ android:textColor="?android:attr/colorAccent"
+ android:text="@*android:string/reset" />
+
+ </LinearLayout>
+
+ <android.support.v7.widget.RecyclerView
+ android:id="@android:id/list"
android:layout_width="@dimen/notification_panel_width"
android:layout_height="0dp"
- android:layout_weight="1"
- android:paddingTop="12dp"
- android:paddingBottom="8dp"
- android:elevation="2dp">
-
- <com.android.systemui.qs.customize.CustomQSPanel
- android:id="@+id/quick_settings_panel"
- android:background="#0000"
- android:layout_width="match_parent"
- android:layout_height="wrap_content" />
-
- </com.android.systemui.tuner.AutoScrollView>
-
- <com.android.systemui.qs.customize.FloatingActionButton
- android:id="@+id/fab"
- android:clickable="true"
- android:layout_width="@dimen/fab_size"
- android:layout_height="@dimen/fab_size"
- android:layout_gravity="bottom|end"
- android:layout_marginEnd="@dimen/fab_margin"
- android:layout_marginBottom="@dimen/fab_margin"
- android:elevation="@dimen/fab_elevation"
- android:background="@drawable/fab_background" />
+ android:layout_weight="1" />
<View
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/qs_customize_tile_frame.xml b/packages/SystemUI/res/layout/qs_customize_tile_frame.xml
new file mode 100644
index 0000000..aaa84fd
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_customize_tile_frame.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp"
+ android:paddingBottom="16dp" />
diff --git a/packages/SystemUI/res/layout/qs_detail_header.xml b/packages/SystemUI/res/layout/qs_detail_header.xml
index 5a96dc3..153e35f 100644
--- a/packages/SystemUI/res/layout/qs_detail_header.xml
+++ b/packages/SystemUI/res/layout/qs_detail_header.xml
@@ -18,10 +18,20 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_alignParentBottom="true"
- android:padding="@dimen/qs_panel_padding"
+ android:paddingEnd="@dimen/qs_panel_padding"
android:visibility="invisible"
- android:background="@drawable/btn_borderless_rect" >
+ android:background="@drawable/btn_borderless_rect"
+ android:gravity="center">
+
+ <ImageView
+ android:id="@*android:id/up"
+ android:layout_width="56dp"
+ android:layout_height="56dp"
+ android:layout_marginEnd="16dp"
+ android:padding="16dp"
+ android:clickable="true"
+ android:background="?android:attr/selectableItemBackground"
+ android:src="?android:attr/homeAsUpIndicator" />
<TextView
android:id="@android:id/title"
@@ -37,4 +47,4 @@
android:clickable="false"
android:textAppearance="@style/TextAppearance.QS.DetailHeader" />
-</com.android.keyguard.AlphaOptimizedLinearLayout>
\ No newline at end of file
+</com.android.keyguard.AlphaOptimizedLinearLayout>
diff --git a/packages/SystemUI/res/layout/qs_detail_item.xml b/packages/SystemUI/res/layout/qs_detail_item.xml
index ccdddf7..5bb4f5d 100644
--- a/packages/SystemUI/res/layout/qs_detail_item.xml
+++ b/packages/SystemUI/res/layout/qs_detail_item.xml
@@ -28,7 +28,7 @@
android:id="@android:id/icon"
android:layout_width="24dp"
android:layout_height="24dp"
- android:layout_marginEnd="12dp" />
+ android:layout_marginEnd="20dp" />
<LinearLayout
android:layout_width="0dp"
diff --git a/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml b/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml
index 0bef513..5fde4f6 100644
--- a/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml
+++ b/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml
@@ -15,6 +15,7 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:layout_height="48dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
style="@style/BrightnessDialogContainer">
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index 6784695..07ac6a5 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -106,7 +106,7 @@
<LinearLayout
android:id="@+id/date_time_group"
android:layout_width="wrap_content"
- android:layout_height="28dp"
+ android:layout_height="25dp"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:orientation="horizontal">
@@ -115,7 +115,7 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginStart="16dp"
- android:layout_marginTop="2dp"
+ android:layout_marginTop="4dp"
android:id="@+id/clock" />
<com.android.systemui.statusbar.policy.DateView
@@ -123,7 +123,7 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginStart="6dp"
- android:layout_marginTop="8dp"
+ android:layout_marginTop="4dp"
android:drawableStart="@drawable/header_dot"
android:drawablePadding="6dp"
android:singleLine="true"
@@ -136,7 +136,7 @@
android:id="@+id/alarm_status"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:layout_marginTop="8dp"
+ android:layout_marginTop="4dp"
android:drawablePadding="6dp"
android:drawableStart="@drawable/ic_access_alarms_small"
android:textColor="#64ffffff"
@@ -152,9 +152,9 @@
android:background="#0000"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="30dp"
- android:layout_marginStart="8dp"
- android:layout_marginEnd="8dp"
+ android:layout_marginTop="25dp"
+ android:layout_marginStart="12dp"
+ android:layout_marginEnd="12dp"
android:layout_alignParentEnd="true"
android:clipChildren="false"
android:clipToPadding="false" />
@@ -164,7 +164,7 @@
layout="@layout/qs_detail_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_alignParentBottom="true"
+ android:layout_marginTop="28dp"
/>
<com.android.systemui.statusbar.AlphaOptimizedImageView
diff --git a/packages/SystemUI/res/layout/status_bar_no_notifications.xml b/packages/SystemUI/res/layout/status_bar_no_notifications.xml
index dd501d4..6f87184 100644
--- a/packages/SystemUI/res/layout/status_bar_no_notifications.xml
+++ b/packages/SystemUI/res/layout/status_bar_no_notifications.xml
@@ -25,9 +25,9 @@
android:id="@+id/no_notifications"
android:layout_width="match_parent"
android:layout_height="64dp"
- android:paddingTop="12dp"
+ android:paddingTop="28dp"
android:gravity="top|center_horizontal"
- android:textColor="#ffffff"
- android:textSize="20sp"
+ android:textColor="#e6ffffff"
+ android:textSize="16sp"
android:text="@string/empty_shade_text"/>
</com.android.systemui.statusbar.EmptyShadeView>
diff --git a/packages/SystemUI/res/layout/status_bar_notification_dismiss_all.xml b/packages/SystemUI/res/layout/status_bar_notification_dismiss_all.xml
index dc7577a..efb273f 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_dismiss_all.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_dismiss_all.xml
@@ -19,15 +19,16 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:visibility="gone"
- android:clipChildren="false"
- android:clipToPadding="false">
+ android:paddingEnd="8dp"
+ android:visibility="gone">
<com.android.systemui.statusbar.DismissViewButton
+ style="@android:style/Widget.Material.Button.Borderless"
android:id="@+id/dismiss_text"
- android:layout_width="48dp"
- android:layout_height="48dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:layout_gravity="end"
android:focusable="true"
- android:background="@drawable/ripple_drawable"
- android:contentDescription="@string/accessibility_clear_all"/>
+ android:contentDescription="@string/accessibility_clear_all"
+ android:text="@string/clear_all_notifications_text"
+ android:textAllCaps="true"/>
</com.android.systemui.statusbar.DismissView>
diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml
index e0affa1..43e7bac 100644
--- a/packages/SystemUI/res/values-land/config.xml
+++ b/packages/SystemUI/res/values-land/config.xml
@@ -38,6 +38,4 @@
while the stack is not focused. -->
<item name="recents_layout_unfocused_range_min" format="float" type="integer">-2</item>
<item name="recents_layout_unfocused_range_max" format="float" type="integer">1.5</item>
-
- <integer name="quick_settings_num_columns">4</integer>
</resources>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index c75a89f..26a81c8 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -19,8 +19,7 @@
<!-- thickness (width) of the navigation bar on phones that require it -->
<dimen name="navigation_bar_size">@*android:dimen/navigation_bar_width</dimen>
- <!-- Standard notification width + gravity -->
- <dimen name="notification_panel_width">@dimen/standard_notification_panel_width</dimen>
+ <!-- Standard notification gravity -->
<integer name="notification_panel_layout_gravity">@integer/standard_notification_panel_layout_gravity</integer>
<dimen name="docked_divider_handle_width">2dp</dimen>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 71f92fd..c0652d8 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -93,4 +93,6 @@
<dimen name="navigation_key_width">128dp</dimen>
<dimen name="navigation_key_padding">25dp</dimen>
+
+ <dimen name="qs_expand_margin">0dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-w550dp-land/config.xml b/packages/SystemUI/res/values-w550dp-land/config.xml
new file mode 100644
index 0000000..71e54a1
--- /dev/null
+++ b/packages/SystemUI/res/values-w550dp-land/config.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+ for different hardware and product builds. -->
+<resources>
+ <integer name="quick_settings_num_columns">4</integer>
+</resources>
diff --git a/wifi/java/android/net/wifi/ScanInfo.aidl b/packages/SystemUI/res/values-w550dp-land/dimens.xml
similarity index 65%
copy from wifi/java/android/net/wifi/ScanInfo.aidl
copy to packages/SystemUI/res/values-w550dp-land/dimens.xml
index 18ae508..4160c83 100644
--- a/wifi/java/android/net/wifi/ScanInfo.aidl
+++ b/packages/SystemUI/res/values-w550dp-land/dimens.xml
@@ -1,5 +1,6 @@
-/**
- * Copyright (c) 2015, The Android Open Source Project
+<?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.
@@ -12,8 +13,11 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- */
+*/
+-->
+<resources>
+ <!-- Standard notification width + gravity -->
+ <dimen name="notification_panel_width">544dp</dimen>
-package android.net.wifi;
-
-parcelable ScanInfo;
+ <dimen name="qs_expand_margin">32dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 32d09e8..e5e5710 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -124,7 +124,7 @@
<dimen name="close_handle_underlap">32dp</dimen>
<!-- Height of the status bar header bar -->
- <dimen name="status_bar_header_height">90dp</dimen>
+ <dimen name="status_bar_header_height">80dp</dimen>
<!-- Height of the status bar header bar when expanded -->
<dimen name="status_bar_header_height_expanded">116dp</dimen>
@@ -186,6 +186,7 @@
<dimen name="qs_detail_empty_text_size">14sp</dimen>
<dimen name="qs_data_usage_text_size">14sp</dimen>
<dimen name="qs_data_usage_usage_text_size">36sp</dimen>
+ <dimen name="qs_expand_margin">0dp</dimen>
<dimen name="segmented_button_spacing">0dp</dimen>
<dimen name="borderless_button_radius">2dp</dimen>
@@ -606,4 +607,7 @@
<dimen name="docked_divider_handle_width">16dp</dimen>
<dimen name="docked_divider_handle_height">2dp</dimen>
+
+ <dimen name="battery_height">14.5dp</dimen>
+ <dimen name="battery_width">9.5dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index c6c448d0..76752e6 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1044,9 +1044,6 @@
<!-- VolumeUI restoration notification: text -->
<string name="volumeui_notification_text">Touch to restore the original.</string>
- <!-- Describes the way 2 names are concatenated. An example would be ", " to produce "Peter Muller, Paul Curry". Please also include a space here if it's appropriate in the language and if it's a RTL language include it on the left. The translation should start and end with " to keep the white space if desired [CHAR LIMIT=5] -->
- <string name="group_summary_concadenation">", "</string>
-
<!-- Toast shown when user unlocks screen and managed profile activity is in the foreground -->
<string name="managed_profile_foreground_toast">You\'re using your work profile</string>
@@ -1400,4 +1397,7 @@
<!-- SysUI Tuner: Label for preview area in navigation bar tuner [CHAR LIMIT=NONE] -->
<string name="preview">Preview</string>
+ <!-- Label for area where tiles can be dragged out of [CHAR LIMIT=60] -->
+ <string name="drag_to_add_tiles">Drag to add tiles</string>
+
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 9931ab9..b078a68 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -241,6 +241,10 @@
parent="@*android:style/TextAppearance.Material.Notification.Info">
</style>
+ <style name="TextAppearance.Material.Notification.HybridNotificationDivider"
+ parent="@*android:style/TextAppearance.Material.Notification">
+ </style>
+
<style name="SearchPanelCircle">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">match_parent</item>
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
index a0dbad4..e770b5d 100755
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
@@ -51,6 +51,8 @@
private static final float BOLT_LEVEL_THRESHOLD = 0.3f; // opaque bolt below this fraction
private final int[] mColors;
+ private final int mIntrinsicWidth;
+ private final int mIntrinsicHeight;
private boolean mShowPercent;
private float mButtonHeightFraction;
@@ -161,6 +163,19 @@
mLightModeBackgroundColor =
context.getColor(R.color.light_mode_icon_color_dual_tone_background);
mLightModeFillColor = context.getColor(R.color.light_mode_icon_color_dual_tone_fill);
+
+ mIntrinsicWidth = context.getResources().getDimensionPixelSize(R.dimen.battery_width);
+ mIntrinsicHeight = context.getResources().getDimensionPixelSize(R.dimen.battery_height);
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return mIntrinsicHeight;
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return mIntrinsicWidth;
}
public void startListening() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSIconView.java b/packages/SystemUI/src/com/android/systemui/qs/QSIconView.java
index ed90904..1df372b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSIconView.java
@@ -24,6 +24,7 @@
import android.view.ViewGroup;
import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
import com.android.systemui.R;
import java.util.Objects;
@@ -96,7 +97,7 @@
protected View createIcon() {
final ImageView icon = new ImageView(mContext);
icon.setId(android.R.id.icon);
- icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
+ icon.setScaleType(ScaleType.FIT_CENTER);
return icon;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index 35000d3..d79f4d4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -76,7 +76,7 @@
private String mTileSpec;
- abstract protected TState newTileState();
+ public abstract TState newTileState();
abstract protected void handleClick();
abstract protected void handleUpdateState(TState state, Object arg);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index 753efb0..f208470 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -18,12 +18,13 @@
import android.content.Context;
import android.content.res.ColorStateList;
+import android.content.res.Configuration;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
-
+import android.widget.Space;
import com.android.systemui.R;
import java.util.ArrayList;
@@ -102,6 +103,8 @@
private static class HeaderTileLayout extends LinearLayout implements QSTileLayout {
+ private final ImageView mDownArrow;
+
public HeaderTileLayout(Context context) {
super(context);
setClipChildren(false);
@@ -111,23 +114,40 @@
int padding =
mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_padding);
- ImageView downArrow = new ImageView(context);
- downArrow.setImageResource(R.drawable.ic_expand_more);
- downArrow.setImageTintList(ColorStateList.valueOf(context.getResources().getColor(
+ mDownArrow = new ImageView(context);
+ mDownArrow.setImageResource(R.drawable.ic_expand_more);
+ mDownArrow.setImageTintList(ColorStateList.valueOf(context.getResources().getColor(
android.R.color.white, null)));
- downArrow.setLayoutParams(generateLayoutParams());
- downArrow.setPadding(padding, padding, padding, padding);
- addView(downArrow);
+ mDownArrow.setLayoutParams(generateLayoutParams());
+ mDownArrow.setPadding(padding, padding, padding, padding);
+ updateDownArrowMargin();
+ addView(mDownArrow);
setOrientation(LinearLayout.HORIZONTAL);
}
@Override
- public void addTile(TileRecord tile) {
- tile.tileView.setLayoutParams(generateLayoutParams());
- addView(tile.tileView, getChildCount() - 1 /* Leave icon at end */);
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ updateDownArrowMargin();
}
- private LayoutParams generateLayoutParams() {
+ private void updateDownArrowMargin() {
+ LayoutParams params = (LayoutParams) mDownArrow.getLayoutParams();
+ params.setMarginStart(mContext.getResources().getDimensionPixelSize(
+ R.dimen.qs_expand_margin));
+ mDownArrow.setLayoutParams(params);
+ }
+
+ @Override
+ public void addTile(TileRecord tile) {
+ addView(tile.tileView, getChildCount() - 1 /* Leave icon at end */,
+ generateLayoutParams());
+ // Add a spacer.
+ addView(new Space(mContext), getChildCount() - 1 /* Leave icon at end */,
+ generateSpaceParams());
+ }
+
+ private LayoutParams generateSpaceParams() {
int size = mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size);
LayoutParams lp = new LayoutParams(0, size);
lp.weight = 1;
@@ -135,9 +155,30 @@
return lp;
}
+ private LayoutParams generateLayoutParams() {
+ int size = mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size);
+ LayoutParams lp = new LayoutParams(size, size);
+ lp.gravity = Gravity.CENTER;
+ return lp;
+ }
+
@Override
public void removeTile(TileRecord tile) {
- removeView(tile.tileView);
+ int childIndex = getChildIndex(tile.tileView);
+ // Remove the tile.
+ removeViewAt(childIndex);
+ // Remove its spacer as well.
+ removeViewAt(childIndex);
+ }
+
+ private int getChildIndex(QSTileBaseView tileView) {
+ final int N = getChildCount();
+ for (int i = 0; i < N; i++) {
+ if (getChildAt(i) == tileView) {
+ return i;
+ }
+ }
+ return -1;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/BlankCustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/customize/BlankCustomTile.java
deleted file mode 100644
index 36bed0d..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/BlankCustomTile.java
+++ /dev/null
@@ -1,91 +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.
- */
-
-package com.android.systemui.qs.customize;
-
-import android.content.ComponentName;
-import android.content.pm.PackageManager;
-import android.content.pm.ServiceInfo;
-import android.graphics.drawable.Drawable;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.MetricsProto.MetricsEvent;
-import com.android.systemui.R;
-import com.android.systemui.qs.QSTile;
-
-public class BlankCustomTile extends QSTile<QSTile.State> {
- public static final String PREFIX = "custom(";
-
- private final ComponentName mComponent;
-
- private BlankCustomTile(Host host, String action) {
- super(host);
- mComponent = ComponentName.unflattenFromString(action);
- }
-
- public static QSTile<?> create(Host host, String spec) {
- if (spec == null || !spec.startsWith(PREFIX) || !spec.endsWith(")")) {
- throw new IllegalArgumentException("Bad custom tile spec: " + spec);
- }
- final String action = spec.substring(PREFIX.length(), spec.length() - 1);
- if (action.isEmpty()) {
- throw new IllegalArgumentException("Empty custom tile spec action");
- }
- return new BlankCustomTile(host, action);
- }
-
- @Override
- public void setListening(boolean listening) {
- }
-
- @Override
- protected State newTileState() {
- return new State();
- }
-
- @Override
- protected void handleUserSwitch(int newUserId) {
- super.handleUserSwitch(newUserId);
- }
-
- @Override
- protected void handleClick() {
- MetricsLogger.action(mContext, getMetricsCategory(), mComponent.getPackageName());
- }
-
- @Override
- protected void handleLongClick() {
- }
-
- @Override
- protected void handleUpdateState(State state, Object arg) {
- try {
- PackageManager pm = mContext.getPackageManager();
- ServiceInfo info = pm.getServiceInfo(mComponent, 0);
- Drawable drawable = info.loadIcon(pm);
- drawable.setTint(mContext.getColor(R.color.qs_tile_tint_active));
- state.icon = new DrawableIcon(drawable);
- state.label = info.loadLabel(pm).toString();
- state.contentDescription = state.label;
- } catch (Exception e) {
- }
- }
-
- @Override
- public int getMetricsCategory() {
- return MetricsEvent.QS_INTENT;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java
deleted file mode 100644
index 286748b..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java
+++ /dev/null
@@ -1,204 +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.
- */
-package com.android.systemui.qs.customize;
-
-import android.app.ActivityManager;
-import android.content.ClipData;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.provider.Settings.Secure;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-
-import com.android.systemui.R;
-import com.android.systemui.qs.QSPanel;
-import com.android.systemui.qs.QSTile;
-import com.android.systemui.qs.external.CustomTile;
-import com.android.systemui.qs.external.TileLifecycleManager;
-import com.android.systemui.statusbar.phone.QSTileHost;
-import com.android.systemui.tuner.TunerService;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * A version of QSPanel that allows tiles to be dragged around rather than
- * clicked on. Dragging starting and receiving is handled in the NonPagedTileLayout,
- * and the saving/ordering is handled by the CustomQSTileHost.
- */
-public class CustomQSPanel extends QSPanel {
-
- private static final String TAG = "CustomQSPanel";
- private static final boolean DEBUG = false;
-
- private List<String> mSavedTiles = Collections.emptyList();
- private ArrayList<String> mStash;
- private List<String> mTiles = new ArrayList<>();
-
- private ArrayList<QSTile<?>> mCurrentTiles = new ArrayList<>();
-
- public CustomQSPanel(Context context, AttributeSet attrs) {
- super(context, attrs);
- mTileLayout = (QSTileLayout) LayoutInflater.from(mContext)
- .inflate(R.layout.qs_customize_layout, mQsContainer, false);
- mQsContainer.addView((View) mTileLayout, 1 /* Between brightness and footer */);
- ((NonPagedTileLayout) mTileLayout).setCustomQsPanel(this);
- removeView(mFooter.getView());
-
- if (DEBUG) Log.d(TAG, "new CustomQSPanel", new Throwable());
- TunerService.get(mContext).addTunable(this, QSTileHost.TILES_SETTING);
- }
-
- @Override
- protected void showDetail(boolean show, Record r) {
- // No detail here.
- }
-
- @Override
- protected void onDetachedFromWindow() {
- // Don't allow the super to unregister the tunable.
- }
-
- @Override
- public void onTuningChanged(String key, String newValue) {
- if (key.equals(QS_SHOW_BRIGHTNESS)) {
- // No Brightness for you.
- super.onTuningChanged(key, "0");
- }
- if (QSTileHost.TILES_SETTING.equals(key)) {
- mSavedTiles = Collections.unmodifiableList(
- QSTileHost.loadTileSpecs(mContext, newValue));
- if (DEBUG) Log.d(TAG, "New saved tiles " + TextUtils.join(",", mSavedTiles));
- }
- }
-
- @Override
- protected void createCustomizePanel() {
- // Already in CustomizePanel.
- }
-
- public void tileSelected(QSTile<?> tile, ClipData currentClip) {
- String sourceSpec = getSpec(currentClip);
- String destSpec = tile.getTileSpec();
- if (!sourceSpec.equals(destSpec)) {
- moveTo(sourceSpec, destSpec);
- }
- }
-
- public ClipData getClip(QSTile<?> tile) {
- String tileSpec = tile.getTileSpec();
- // TODO: Something better than plain text.
- // TODO: Once using something better than plain text, stop listening to non-QS drag events.
- return ClipData.newPlainText(tileSpec, tileSpec);
- }
-
- public String getSpec(ClipData data) {
- return data.getItemAt(0).getText().toString();
- }
-
- public void setSavedTiles() {
- if (DEBUG) Log.d(TAG, "setSavedTiles " + TextUtils.join(",", mSavedTiles));
- setTiles(mSavedTiles);
- }
-
- public void saveCurrentTiles() {
- mHost.changeTiles(mSavedTiles, mTiles);
- }
-
- public void stashCurrentTiles() {
- mStash = new ArrayList<>(mTiles);
- }
-
- public void unstashTiles() {
- setTiles(mStash);
- }
-
- @Override
- public void setTiles(Collection<QSTile<?>> tiles) {
- setTilesInternal();
- }
-
- private void setTilesInternal() {
- if (DEBUG) Log.d(TAG, "Set tiles internal");
- for (int i = 0; i < mCurrentTiles.size(); i++) {
- mCurrentTiles.get(i).destroy();
- }
- mCurrentTiles.clear();
- for (int i = 0; i < mTiles.size(); i++) {
- if (mTiles.get(i).startsWith(CustomTile.PREFIX)) {
- QSTile<?> tile = BlankCustomTile.create(mHost, mTiles.get(i));
- tile.setTileSpec(mTiles.get(i));
- mCurrentTiles.add(tile);
- } else {
- QSTile<?> tile = mHost.createTile(mTiles.get(i));
- if (tile != null) {
- tile.setTileSpec(mTiles.get(i));
- mCurrentTiles.add(tile);
- } else {
- if (DEBUG) Log.d(TAG, "Skipping " + mTiles.get(i));
- }
- }
- }
- super.setTiles(mCurrentTiles);
- }
-
- public void addTile(String spec) {
- if (DEBUG) Log.d(TAG, "addTile " + spec);
- mTiles.add(spec);
- setTilesInternal();
- }
-
- public void moveTo(String from, String to) {
- if (DEBUG) Log.d(TAG, "moveTo " + from + " " + to);
- int fromIndex = mTiles.indexOf(from);
- if (fromIndex < 0) {
- Log.e(TAG, "Unknown from tile " + from);
- return;
- }
- int index = mTiles.indexOf(to);
- if (index < 0) {
- Log.e(TAG, "Unknown to tile " + to);
- return;
- }
- mTiles.remove(fromIndex);
- mTiles.add(index, from);
- setTilesInternal();
- }
-
- public void remove(String spec) {
- if (!mTiles.remove(spec)) {
- Log.e(TAG, "Unknown remove spec " + spec);
- }
- setTilesInternal();
- }
-
- public void setTiles(List<String> tiles) {
- if (DEBUG) Log.d(TAG, "Set tiles " + TextUtils.join(",", tiles));
- mTiles = new ArrayList<>(tiles);
- setTilesInternal();
- }
-
- public Collection<QSTile<?>> getTiles() {
- return mCurrentTiles;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/DropButton.java b/packages/SystemUI/src/com/android/systemui/qs/customize/DropButton.java
deleted file mode 100644
index 3135408..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/DropButton.java
+++ /dev/null
@@ -1,70 +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.
-*/
-package com.android.systemui.qs.customize;
-
-import android.content.ClipData;
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.DragEvent;
-import android.view.View;
-import android.view.View.OnDragListener;
-import android.widget.TextView;
-
-public class DropButton extends TextView implements OnDragListener {
-
- private OnDropListener mListener;
-
- public DropButton(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- // TODO: Don't do this, instead make this view the right size...
- ((View) getParent()).setOnDragListener(this);
- }
-
- public void setOnDropListener(OnDropListener listener) {
- mListener = listener;
- }
-
- private void setHovering(boolean hovering) {
- setAlpha(hovering ? .3f : 1);
- }
-
- @Override
- public boolean onDrag(View v, DragEvent event) {
- switch (event.getAction()) {
- case DragEvent.ACTION_DRAG_ENTERED:
- setHovering(true);
- break;
- case DragEvent.ACTION_DROP:
- if (mListener != null) {
- mListener.onDrop(this, event.getClipData());
- }
- case DragEvent.ACTION_DRAG_EXITED:
- case DragEvent.ACTION_DRAG_ENDED:
- setHovering(false);
- break;
- }
- return true;
- }
-
- public interface OnDropListener {
- void onDrop(View v, ClipData data);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/FloatingActionButton.java b/packages/SystemUI/src/com/android/systemui/qs/customize/FloatingActionButton.java
deleted file mode 100644
index 8791a10..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/FloatingActionButton.java
+++ /dev/null
@@ -1,49 +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.
- */
-
-package com.android.systemui.qs.customize;
-
-import android.animation.AnimatorInflater;
-import android.content.Context;
-import android.graphics.Outline;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewOutlineProvider;
-import android.widget.ImageView;
-
-import com.android.systemui.R;
-
-public class FloatingActionButton extends ImageView {
-
- public FloatingActionButton(Context context, AttributeSet attrs) {
- super(context, attrs);
- setScaleType(ScaleType.CENTER);
- setStateListAnimator(AnimatorInflater.loadStateListAnimator(context, R.anim.fab_elevation));
- setOutlineProvider(new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- outline.setOval(0, 0, getWidth(), getHeight());
- }
- });
- setClipToOutline(true);
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
- invalidateOutline();
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java
deleted file mode 100644
index 98c7be4..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java
+++ /dev/null
@@ -1,184 +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.
- */
-package com.android.systemui.qs.customize;
-
-import android.content.ClipData;
-import android.content.Context;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.DragEvent;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnTouchListener;
-import android.widget.LinearLayout;
-
-import com.android.systemui.R;
-import com.android.systemui.qs.PagedTileLayout;
-import com.android.systemui.qs.PagedTileLayout.TilePage;
-import com.android.systemui.qs.QSPanel.QSTileLayout;
-import com.android.systemui.qs.QSPanel.TileRecord;
-import com.android.systemui.qs.QSTile;
-
-import java.util.ArrayList;
-
-/**
- * Similar to PagedTileLayout, except that instead of pages it lays them out
- * vertically and expects to be inside a ScrollView.
- * @see CustomQSPanel
- */
-public class NonPagedTileLayout extends LinearLayout implements QSTileLayout, OnTouchListener {
-
- private final ArrayList<TilePage> mPages = new ArrayList<>();
- private final ArrayList<TileRecord> mTiles = new ArrayList<TileRecord>();
- private CustomQSPanel mPanel;
- private final Rect mHitRect = new Rect();
-
- private ClipData mCurrentClip;
- private View mCurrentView;
-
- public NonPagedTileLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- TilePage page = (PagedTileLayout.TilePage) findViewById(R.id.tile_page);
- page.setMaxRows(3 /* First page only gets 3 */);
- mPages.add(page);
- }
-
- public void setCustomQsPanel(CustomQSPanel qsPanel) {
- mPanel = qsPanel;
- }
-
- @Override
- public void addTile(TileRecord record) {
- mTiles.add(record);
- distributeTiles();
- if (record.tileView.getTag() == record.tile) {
- return;
- }
- record.tileView.setTag(record.tile);
- record.tileView.setVisibility(View.VISIBLE);
- record.tileView.init(null, null);
- record.tileView.setOnTouchListener(this);
- if (mCurrentClip != null && mCurrentClip.getItemAt(0)
- .getText().toString().equals(record.tile.getTileSpec())) {
- record.tileView.setAlpha(.3f);
- mCurrentView = record.tileView;
- }
- }
-
- @Override
- public void removeTile(TileRecord tile) {
- if (mTiles.remove(tile)) {
- distributeTiles();
- }
- }
-
- private void distributeTiles() {
- final int NP = mPages.size();
- for (int i = 0; i < NP; i++) {
- mPages.get(i).removeAllViews();
- }
- int index = 0;
- final int NT = mTiles.size();
- for (int i = 0; i < NT; i++) {
- TileRecord tile = mTiles.get(i);
- mPages.get(index).addTile(tile);
- // Keep everything in one layout for now.
- if (false && mPages.get(index).isFull()) {
- if (++index == mPages.size()) {
- LayoutInflater inflater = LayoutInflater.from(mContext);
- inflater.inflate(R.layout.horizontal_divider, this);
- mPages.add((TilePage) inflater.inflate(R.layout.qs_paged_page, this, false));
- addView(mPages.get(mPages.size() - 1));
- }
- }
- }
- }
-
- @Override
- public int getOffsetTop(TileRecord tile) {
- // No touch feedback, so this isn't required.
- return 0;
- }
-
- @Override
- public boolean updateResources() {
- return false;
- }
-
- @Override
- public boolean onDragEvent(DragEvent event) {
- switch (event.getAction()) {
- case DragEvent.ACTION_DRAG_LOCATION:
- float x = event.getX();
- float y = event.getY();
- final int NP = mPages.size();
- for (int i = 0; i < NP; i++) {
- TilePage page = mPages.get(i);
- if (contains(page, x, y)) {
- x -= page.getLeft();
- y -= page.getTop();
- final int NC = page.getChildCount();
- for (int j = 0; j < NC; j++) {
- View child = page.getChildAt(j);
- if (contains(child, x, y)) {
- mPanel.tileSelected((QSTile<?>) child.getTag(), mCurrentClip);
- }
- }
- break;
- }
- }
- break;
- case DragEvent.ACTION_DRAG_ENDED:
- onDragEnded();
- break;
- }
- return true;
- }
-
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- // Stash the current tiles, in case the drop is on info, that we can restore
- // the previous state.
- mPanel.stashCurrentTiles();
- mCurrentView = v;
- mCurrentClip = mPanel.getClip((QSTile<?>) v.getTag());
- View.DragShadowBuilder shadow = new View.DragShadowBuilder(v);
- ((View) getParent().getParent()).startDrag(mCurrentClip, shadow, null, 0);
- v.setAlpha(.3f);
- return true;
- }
- return false;
- }
-
- public void onDragEnded() {
- mCurrentView.setAlpha(1f);
- mCurrentView = null;
- mCurrentClip = null;
- }
-
- private boolean contains(View v, float x, float y) {
- v.getHitRect(mHitRect);
- return mHitRect.contains((int) x, (int) y);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index a6c7fe4..edcccac 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -16,38 +16,25 @@
package com.android.systemui.qs.customize;
import android.animation.Animator;
-import android.content.ClipData;
+import android.animation.Animator.AnimatorListener;
import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnCancelListener;
-import android.content.DialogInterface.OnDismissListener;
+import android.support.v7.widget.DefaultItemAnimator;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.helper.ItemTouchHelper;
import android.util.AttributeSet;
-import android.util.Log;
-import android.util.TypedValue;
import android.view.ContextThemeWrapper;
-import android.view.DragEvent;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.view.WindowManager;
import android.widget.LinearLayout;
-import android.widget.ListView;
-import android.widget.Toolbar;
-import android.widget.Toolbar.OnMenuItemClickListener;
-
import com.android.systemui.R;
import com.android.systemui.qs.QSDetailClipper;
-import com.android.systemui.qs.QSTile.Host.Callback;
-import com.android.systemui.qs.customize.DropButton.OnDropListener;
-import com.android.systemui.qs.customize.TileAdapter.TileSelectedListener;
+import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.statusbar.phone.QSTileHost;
-import com.android.systemui.statusbar.phone.SystemUIDialog;
import java.util.ArrayList;
+import java.util.List;
/**
* Allows full-screen customization of QS, through show() and hide().
@@ -55,26 +42,19 @@
* This adds itself to the status bar window, so it can appear on top of quick settings and
* *someday* do fancy animations to get into/out of it.
*/
-public class QSCustomizer extends LinearLayout implements OnMenuItemClickListener, Callback,
- OnDropListener, OnClickListener, Animator.AnimatorListener, TileSelectedListener,
- OnCancelListener, OnDismissListener {
+public class QSCustomizer extends LinearLayout implements AnimatorListener, OnClickListener {
- private static final int MENU_SAVE = Menu.FIRST;
- private static final int MENU_RESET = Menu.FIRST + 1;
private final QSDetailClipper mClipper;
private PhoneStatusBar mPhoneStatusBar;
- private Toolbar mToolbar;
- private ViewGroup mDragButtons;
- private CustomQSPanel mQsPanel;
-
private boolean isShown;
- private DropButton mInfoButton;
- private DropButton mRemoveButton;
- private FloatingActionButton mFab;
- private SystemUIDialog mDialog;
private QSTileHost mHost;
+ private RecyclerView mRecyclerView;
+ private TileAdapter mTileAdapter;
+ private View mClose;
+ private View mSave;
+ private View mReset;
public QSCustomizer(Context context, AttributeSet attrs) {
super(new ContextThemeWrapper(context, android.R.style.Theme_Material), attrs);
@@ -83,59 +63,42 @@
public void setHost(QSTileHost host) {
mHost = host;
- mHost.addCallback(this);
mPhoneStatusBar = host.getPhoneStatusBar();
- mQsPanel.setTiles(mHost.getTiles());
- mQsPanel.setHost(mHost);
- mQsPanel.setSavedTiles();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mToolbar = (Toolbar) findViewById(com.android.internal.R.id.action_bar);
- TypedValue value = new TypedValue();
- mContext.getTheme().resolveAttribute(android.R.attr.homeAsUpIndicator, value, true);
- mToolbar.setNavigationIcon(
- getResources().getDrawable(R.drawable.ic_close_white, mContext.getTheme()));
- mToolbar.setNavigationOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- hide(0, 0);
- }
- });
- mToolbar.setOnMenuItemClickListener(this);
- mToolbar.getMenu().add(Menu.NONE, MENU_SAVE, 0, mContext.getString(R.string.save))
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
- mToolbar.getMenu().add(Menu.NONE, MENU_RESET, 0,
- mContext.getString(com.android.internal.R.string.reset));
+ mClose = findViewById(R.id.close);
+ mSave = findViewById(R.id.save);
+ mReset = findViewById(R.id.reset);
+ mClose.setOnClickListener(this);
+ mSave.setOnClickListener(this);
+ mReset.setOnClickListener(this);
- mQsPanel = (CustomQSPanel) findViewById(R.id.quick_settings_panel);
-
- mDragButtons = (ViewGroup) findViewById(R.id.drag_buttons);
- setDragging(false);
-
- mInfoButton = (DropButton) findViewById(R.id.info_button);
- mInfoButton.setOnDropListener(this);
- mRemoveButton = (DropButton) findViewById(R.id.remove_button);
- mRemoveButton.setOnDropListener(this);
-
- mFab = (FloatingActionButton) findViewById(R.id.fab);
- mFab.setImageResource(R.drawable.ic_add);
- mFab.setOnClickListener(this);
+ mRecyclerView = (RecyclerView) findViewById(android.R.id.list);
+ mTileAdapter = new TileAdapter(getContext());
+ mRecyclerView.setAdapter(mTileAdapter);
+ new ItemTouchHelper(mTileAdapter.getCallback()).attachToRecyclerView(mRecyclerView);
+ GridLayoutManager layout = new GridLayoutManager(getContext(), 3);
+ layout.setSpanSizeLookup(mTileAdapter.getSizeLookup());
+ mRecyclerView.setLayoutManager(layout);
+ mRecyclerView.addItemDecoration(mTileAdapter.getItemDecoration());
+ DefaultItemAnimator animator = new DefaultItemAnimator();
+ animator.setMoveDuration(TileAdapter.MOVE_DURATION);
+ mRecyclerView.setItemAnimator(animator);
}
public void show(int x, int y) {
isShown = true;
- mQsPanel.setSavedTiles();
mPhoneStatusBar.getStatusBarWindow().addView(this);
- mQsPanel.setListening(true);
+ setTileSpecs();
mClipper.animateCircularClip(x, y, true, this);
+ new TileQueryHelper(mContext, mHost).setListener(mTileAdapter);
}
public void hide(int x, int y) {
isShown = false;
- mQsPanel.setListening(false);
mClipper.animateCircularClip(x, y, false, this);
}
@@ -149,109 +112,35 @@
for (String tile : defTiles.split(",")) {
tiles.add(tile);
}
- mQsPanel.setTiles(tiles);
+ mTileAdapter.setTileSpecs(tiles);
}
- private void setDragging(boolean dragging) {
- mToolbar.setVisibility(!dragging ? View.VISIBLE : View.INVISIBLE);
+ private void setTileSpecs() {
+ List<String> specs = new ArrayList<>();
+ for (QSTile tile : mHost.getTiles()) {
+ specs.add(tile.getTileSpec());
+ }
+ mTileAdapter.setTileSpecs(specs);
}
private void save() {
- Log.d("CustomQSPanel", "Save!");
- mQsPanel.saveCurrentTiles();
- // TODO: At save button.
- hide(0, 0);
- }
-
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- switch (item.getItemId()) {
- case MENU_SAVE:
- Log.d("CustomQSPanel", "Save...");
- save();
- break;
- case MENU_RESET:
- reset();
- break;
- }
- return true;
- }
-
- @Override
- public void onTileSelected(String spec) {
- if (mDialog != null) {
- mQsPanel.addTile(spec);
- mDialog.dismiss();
- }
- }
-
- @Override
- public void onTilesChanged() {
- mQsPanel.setTiles(mHost.getTiles());
- }
-
- public boolean onDragEvent(DragEvent event) {
- switch (event.getAction()) {
- case DragEvent.ACTION_DRAG_STARTED:
- setDragging(true);
- break;
- case DragEvent.ACTION_DRAG_ENDED:
- setDragging(false);
- break;
- }
- return true;
- }
-
- public void onDrop(View v, ClipData data) {
- if (v == mRemoveButton) {
- mQsPanel.remove(mQsPanel.getSpec(data));
- } else if (v == mInfoButton) {
- mQsPanel.unstashTiles();
- SystemUIDialog dialog = new SystemUIDialog(mContext);
- dialog.setTitle(mQsPanel.getSpec(data));
- dialog.setPositiveButton(R.string.ok, null);
- dialog.show();
- }
+ mTileAdapter.saveSpecs(mHost);
+ hide((int) mSave.getX() + mSave.getWidth() / 2, (int) mSave.getY() + mSave.getHeight() / 2);
}
@Override
public void onClick(View v) {
- if (mFab == v) {
- mDialog = new SystemUIDialog(mContext,
- android.R.style.Theme_Material_Dialog);
- View view = LayoutInflater.from(mContext).inflate(R.layout.qs_add_tiles_list, null);
- ListView listView = (ListView) view.findViewById(android.R.id.list);
- TileAdapter adapter = new TileAdapter(mContext, mQsPanel.getTiles(), mHost);
- adapter.setListener(this);
- listView.setDivider(null);
- listView.setDividerHeight(0);
- listView.setAdapter(adapter);
- listView.setEmptyView(view.findViewById(R.id.empty_text));
- mDialog.setView(view);
- mDialog.setOnDismissListener(this);
- mDialog.setOnCancelListener(this);
- mDialog.show();
- // Too lazy to figure out what this will be now, but it should probably be something
- // besides just a dialog.
- // For now, just make it big.
- WindowManager.LayoutParams params = mDialog.getWindow().getAttributes();
- params.width = WindowManager.LayoutParams.MATCH_PARENT;
- params.height = WindowManager.LayoutParams.WRAP_CONTENT;
- mDialog.getWindow().setAttributes(params);
+ if (v == mClose) {
+ hide((int) mClose.getX() + mClose.getWidth() / 2,
+ (int) mClose.getY() + mClose.getHeight() / 2);
+ } else if (v == mSave) {
+ save();
+ } else if (v == mReset) {
+ reset();
}
}
@Override
- public void onDismiss(DialogInterface dialog) {
- mDialog = null;
- }
-
- @Override
- public void onCancel(DialogInterface dialog) {
- mDialog = null;
- }
-
- @Override
public void onAnimationEnd(Animator animation) {
if (!isShown) {
mPhoneStatusBar.getStatusBarWindow().removeView(this);
@@ -274,4 +163,4 @@
public void onAnimationRepeat(Animator animation) {
// Don't care.
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index b72789e..fb3818c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -1,262 +1,293 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2016 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
- * 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.
+ * 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.qs.customize;
-import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.graphics.drawable.Drawable;
-import android.os.AsyncTask;
-import android.os.Handler;
-import android.os.Looper;
-import android.service.quicksettings.TileService;
-import android.util.Log;
+import android.graphics.Canvas;
+import android.graphics.drawable.ColorDrawable;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.widget.GridLayoutManager.SpanSizeLookup;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.ItemDecoration;
+import android.support.v7.widget.RecyclerView.State;
+import android.support.v7.widget.RecyclerView.ViewHolder;
+import android.support.v7.widget.helper.ItemTouchHelper;
+import android.support.v7.widget.helper.ItemTouchHelper.Callback;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.View.OnClickListener;
import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.GridLayout;
-import android.widget.ImageView;
-import android.widget.TextView;
-
+import android.widget.FrameLayout;
import com.android.systemui.R;
-import com.android.systemui.qs.QSTile;
-import com.android.systemui.qs.QSTile.Icon;
-import com.android.systemui.qs.external.CustomTile;
+import com.android.systemui.qs.QSIconView;
+import com.android.systemui.qs.QSTileView;
+import com.android.systemui.qs.customize.TileAdapter.Holder;
+import com.android.systemui.qs.customize.TileQueryHelper.TileInfo;
+import com.android.systemui.qs.customize.TileQueryHelper.TileStateListener;
import com.android.systemui.statusbar.phone.QSTileHost;
import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
import java.util.List;
-public class TileAdapter extends BaseAdapter {
+public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileStateListener {
- private static final String TAG = "TileAdapter";
+ private static final long DRAG_LENGTH = 100;
+ private static final float DRAG_SCALE = 1.2f;
+ public static final long MOVE_DURATION = 150;
- private final ArrayList<TileGroup> mGroups = new ArrayList<>();
+ private static final int TYPE_TILE = 0;
+ private static final int TYPE_EDIT = 1;
+
private final Context mContext;
- private TileSelectedListener mListener;
- private ArrayList<String> mCurrentTiles;
+ private final List<TileInfo> mTiles = new ArrayList<>();
+ private int mDividerIndex;
+ private List<String> mCurrentSpecs;
+ private List<TileInfo> mOtherTiles;
+ private List<TileInfo> mAllTiles;
- public TileAdapter(Context context, Collection<QSTile<?>> currentTiles, QSTileHost host) {
+ private Holder mCurrentDrag;
+
+ public TileAdapter(Context context) {
mContext = context;
- addSystemTiles(currentTiles, host);
- // TODO: Live?
- }
-
- private void addSystemTiles(Collection<QSTile<?>> currentTiles, QSTileHost host) {
- try {
- ArrayList<String> tileSpecs = new ArrayList<>();
- for (QSTile<?> tile : currentTiles) {
- tileSpecs.add(tile.getTileSpec());
- }
- mCurrentTiles = tileSpecs;
- final TileGroup group = new TileGroup("com.android.settings", mContext);
- boolean hasColorMod = host.getDisplayController().isEnabled();
- String possible = mContext.getString(R.string.quick_settings_tiles_default)
- + ",hotspot,inversion,saver" + (hasColorMod ? ",colors" : "");
- String[] possibleTiles = possible.split(",");
- for (int i = 0; i < possibleTiles.length; i++) {
- final String spec = possibleTiles[i];
- if (spec.startsWith("q")) {
- // Quick tiles can't be customized.
- continue;
- }
- if (tileSpecs.contains(spec)) {
- Log.d(TAG, "Skipping " + spec);
- continue;
- }
- Log.d(TAG, "Trying " + spec);
- final QSTile<?> tile = host.createTile(spec);
- if (tile == null) {
- continue;
- }
- // Bad, bad, very bad.
- tile.setListening(true);
- tile.clearState();
- tile.refreshState();
- tile.setListening(false);
- new Handler(host.getLooper()).post(new Runnable() {
- @Override
- public void run() {
- group.addTile(spec, tile.getState().icon, tile.getState().label, mContext);
- }
- });
- }
- // Error: Badness (10000).
- // Serialize this work after the host's looper's queue is empty.
- new Handler(host.getLooper()).post(new Runnable() {
- @Override
- public void run() {
- new Handler(Looper.getMainLooper()).post(new Runnable() {
- @Override
- public void run() {
- if (group.mTiles.size() > 0) {
- mGroups.add(group);
- notifyDataSetChanged();
- }
- new QueryTilesTask().execute();
- }
- });
- }
- });
- } catch (NameNotFoundException e) {
- Log.e(TAG, "Couldn't load system tiles", e);
- }
- }
-
- public void setListener(TileSelectedListener listener) {
- mListener = listener;
- }
-
- @Override
- public int getCount() {
- return mGroups.size();
- }
-
- @Override
- public Object getItem(int position) {
- return mGroups.get(position);
+ setHasStableIds(true);
}
@Override
public long getItemId(int position) {
- return position;
+ return mTiles.get(position) != null ? mAllTiles.indexOf(mTiles.get(position)) : -1;
+ }
+
+ public Callback getCallback() {
+ return mCallbacks;
+ }
+
+ public ItemDecoration getItemDecoration() {
+ return mDecoration;
+ }
+
+ public void saveSpecs(QSTileHost host) {
+ List<String> newSpecs = new ArrayList<>();
+ for (int i = 0; mTiles.get(i) != null; i++) {
+ newSpecs.add(mTiles.get(i).spec);
+ }
+ host.changeTiles(mCurrentSpecs, newSpecs);
+ setTileSpecs(newSpecs);
+ }
+
+ public void setTileSpecs(List<String> currentSpecs) {
+ mCurrentSpecs = currentSpecs;
+ recalcSpecs();
}
@Override
- public View getView(int position, View convertView, ViewGroup parent) {
- return mGroups.get(position).getView(mContext, convertView, parent, mListener);
+ public void onTilesChanged(List<TileInfo> tiles) {
+ mAllTiles = tiles;
+ recalcSpecs();
}
- private static class TileGroup {
- private final ArrayList<TileInfo> mTiles = new ArrayList<>();
- private CharSequence mLabel;
- private Drawable mIcon;
-
- public TileGroup(String pkg, Context context) throws NameNotFoundException {
- PackageManager pm = context.getPackageManager();
- ApplicationInfo info = pm.getApplicationInfo(pkg, 0);
- mLabel = info.loadLabel(pm);
- mIcon = info.loadIcon(pm);
- Log.d(TAG, "Added " + mLabel);
+ private void recalcSpecs() {
+ if (mCurrentSpecs == null || mAllTiles == null) {
+ return;
}
-
- private void addTile(String spec, Drawable icon, CharSequence label) {
- TileInfo info = new TileInfo();
- info.label = label;
- info.drawable = icon;
- info.spec = spec;
- mTiles.add(info);
+ mOtherTiles = new ArrayList<TileInfo>(mAllTiles);
+ mTiles.clear();
+ for (int i = 0; i < mCurrentSpecs.size(); i++) {
+ mTiles.add(getAndRemoveOther(mCurrentSpecs.get(i)));
}
+ mTiles.add(null);
+ mTiles.addAll(mOtherTiles);
+ mDividerIndex = mTiles.indexOf(null);
+ notifyDataSetChanged();
+ }
- private void addTile(String spec, Icon icon, CharSequence label, Context context) {
- addTile(spec, icon != null ? icon.getDrawable(context) : null, label);
- }
-
- private View getView(Context context, View convertView, ViewGroup parent,
- final TileSelectedListener listener) {
- if (convertView == null) {
- convertView = LayoutInflater.from(context).inflate(R.layout.tile_listing, parent,
- false);
+ private TileInfo getAndRemoveOther(String s) {
+ for (int i = 0; i < mOtherTiles.size(); i++) {
+ if (mOtherTiles.get(i).spec.equals(s)) {
+ return mOtherTiles.remove(i);
}
- ((TextView) convertView.findViewById(android.R.id.title)).setText(mLabel);
- ((ImageView) convertView.findViewById(android.R.id.icon)).setImageDrawable(mIcon);
- GridLayout grid = (GridLayout) convertView.findViewById(R.id.tile_grid);
- final int N = mTiles.size();
- if (grid.getChildCount() != N) {
- grid.removeAllViews();
+ }
+ return null;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ if (mTiles.get(position) == null) {
+ return TYPE_EDIT;
+ }
+ return TYPE_TILE;
+ }
+
+ @Override
+ public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
+ final Context context = parent.getContext();
+ LayoutInflater inflater = LayoutInflater.from(context);
+ if (viewType == 1) {
+ return new Holder(inflater.inflate(R.layout.qs_customize_divider, parent, false));
+ }
+ FrameLayout frame = (FrameLayout) inflater.inflate(R.layout.qs_customize_tile_frame, parent,
+ false);
+ frame.addView(new QSTileView(context, new QSIconView(context)));
+ return new Holder(frame);
+ }
+
+ @Override
+ public int getItemCount() {
+ return mTiles.size();
+ }
+
+ @Override
+ public void onBindViewHolder(Holder holder, int position) {
+ if (holder.getItemViewType() == TYPE_EDIT) return;
+
+ TileInfo info = mTiles.get(position);
+ holder.mTileView.onStateChanged(info.state);
+ }
+
+ public SpanSizeLookup getSizeLookup() {
+ return mSizeLookup;
+ }
+
+ public class Holder extends ViewHolder {
+ private QSTileView mTileView;
+
+ public Holder(View itemView) {
+ super(itemView);
+ if (itemView instanceof FrameLayout) {
+ mTileView = (QSTileView) ((FrameLayout) itemView).getChildAt(0);
}
- for (int i = 0; i < N; i++) {
- if (grid.getChildCount() <= i) {
- grid.addView(createTile(context));
- }
- View view = grid.getChildAt(i);
- final TileInfo tileInfo = mTiles.get(i);
- ((ImageView) view.findViewById(R.id.tile_icon)).setImageDrawable(tileInfo.drawable);
- ((TextView) view.findViewById(R.id.tile_label)).setText(tileInfo.label);
- view.setClickable(true);
- view.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- listener.onTileSelected(tileInfo.spec);
- }
- });
- }
- return convertView;
}
- private View createTile(Context context) {
- return LayoutInflater.from(context).inflate(R.layout.qs_add_tile_layout, null);
+ public void startDrag() {
+ itemView.animate()
+ .setDuration(DRAG_LENGTH)
+ .scaleX(DRAG_SCALE)
+ .scaleY(DRAG_SCALE);
+ mTileView.findViewById(R.id.tile_label).animate()
+ .setDuration(DRAG_LENGTH)
+ .alpha(0);
+ }
+
+ public void stopDrag() {
+ itemView.animate()
+ .setDuration(DRAG_LENGTH)
+ .scaleX(1)
+ .scaleY(1);
+ mTileView.findViewById(R.id.tile_label).animate()
+ .setDuration(DRAG_LENGTH)
+ .alpha(1);
}
}
- private static class TileInfo {
- private String spec;
- private Drawable drawable;
- private CharSequence label;
- }
-
- private class QueryTilesTask extends AsyncTask<Void, Void, Collection<TileGroup>> {
+ private final SpanSizeLookup mSizeLookup = new SpanSizeLookup() {
@Override
- protected Collection<TileGroup> doInBackground(Void... params) {
- HashMap<String, TileGroup> pkgMap = new HashMap<>();
- PackageManager pm = mContext.getPackageManager();
- // TODO: Handle userness.
- List<ResolveInfo> services = pm.queryIntentServices(
- new Intent(TileService.ACTION_QS_TILE), 0);
- for (ResolveInfo info : services) {
- String packageName = info.serviceInfo.packageName;
- ComponentName componentName = new ComponentName(packageName, info.serviceInfo.name);
- String spec = CustomTile.toSpec(componentName);
- if (mCurrentTiles.contains(spec)) {
+ public int getSpanSize(int position) {
+ return getItemViewType(position) == TYPE_EDIT ? 3 : 1;
+ }
+ };
+
+ private final ItemDecoration mDecoration = new ItemDecoration() {
+ // TODO: Move this to resource.
+ private final ColorDrawable mDrawable = new ColorDrawable(0xff384248);
+
+ @Override
+ public void onDraw(Canvas c, RecyclerView parent, State state) {
+ super.onDraw(c, parent, state);
+
+ final int childCount = parent.getChildCount();
+ final int width = parent.getWidth();
+ final int bottom = parent.getBottom();
+ for (int i = 0; i < childCount; i++) {
+ final View child = parent.getChildAt(i);
+ final ViewHolder holder = parent.getChildViewHolder(child);
+ if (holder.getAdapterPosition() < mDividerIndex) {
continue;
}
- try {
- TileGroup group = pkgMap.get(packageName);
- if (group == null) {
- group = new TileGroup(packageName, mContext);
- pkgMap.put(packageName, group);
- }
- Drawable icon = info.serviceInfo.loadIcon(pm);
- CharSequence label = info.serviceInfo.loadLabel(pm);
- group.addTile(spec, icon, label != null ? label.toString() : "null");
- } catch (NameNotFoundException e) {
- Log.w(TAG, "Couldn't find resolved package... " + packageName, e);
- }
+
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
+ .getLayoutParams();
+ final int top = child.getTop() + params.topMargin +
+ Math.round(ViewCompat.getTranslationY(child));
+ // Draw full width, in case there aren't tiles all the way across.
+ mDrawable.setBounds(0, top, width, bottom);
+ mDrawable.draw(c);
+ break;
}
- return pkgMap.values();
+ }
+ };
+
+ private final ItemTouchHelper.Callback mCallbacks = new ItemTouchHelper.Callback() {
+
+ @Override
+ public boolean isLongPressDragEnabled() {
+ return true;
}
@Override
- protected void onPostExecute(Collection<TileGroup> result) {
- mGroups.addAll(result);
- notifyDataSetChanged();
+ public boolean isItemViewSwipeEnabled() {
+ return false;
}
- }
- public interface TileSelectedListener {
- void onTileSelected(String spec);
- }
+ @Override
+ public void onSelectedChanged(ViewHolder viewHolder, int actionState) {
+ super.onSelectedChanged(viewHolder, actionState);
+ if (mCurrentDrag != null) {
+ mCurrentDrag.stopDrag();
+ }
+ if (viewHolder != null) {
+ mCurrentDrag = (Holder) viewHolder;
+ mCurrentDrag.startDrag();
+ }
+ }
+
+ @Override
+ public int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHolder) {
+ if (viewHolder.getItemViewType() == TYPE_EDIT) {
+ return makeMovementFlags(0, 0);
+ }
+ int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.RIGHT
+ | ItemTouchHelper.LEFT;
+ return makeMovementFlags(dragFlags, 0);
+ }
+
+ @Override
+ public boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder, ViewHolder target) {
+ int from = viewHolder.getAdapterPosition();
+ int to = target.getAdapterPosition();
+ if (to > mDividerIndex) {
+ if (from < mDividerIndex) {
+ to = mDividerIndex;
+ } else {
+ return false;
+ }
+ }
+ if (target.getItemViewType() == TYPE_EDIT && from < mDividerIndex) {
+ to++;
+ }
+ move(from, to, mTiles);
+ mDividerIndex = mTiles.indexOf(null);
+ notifyItemMoved(from, to);
+ return true;
+ }
+
+ private <T> void move(int from, int to, List<T> list) {
+ list.add(from > to ? to : to + 1, list.get(from));
+ list.remove(from > to ? from + 1 : from);
+ }
+
+ @Override
+ public void onSwiped(ViewHolder viewHolder, int direction) {
+ }
+ };
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
new file mode 100644
index 0000000..6840e08
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -0,0 +1,151 @@
+/*
+ * 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.qs.customize;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Looper;
+import android.service.quicksettings.TileService;
+import com.android.systemui.R;
+import com.android.systemui.qs.QSTile;
+import com.android.systemui.qs.QSTile.DrawableIcon;
+import com.android.systemui.qs.external.CustomTile;
+import com.android.systemui.statusbar.phone.QSTileHost;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public class TileQueryHelper {
+
+ private static final String TAG = "TileQueryHelper";
+
+ private final ArrayList<TileInfo> mTiles = new ArrayList<>();
+ private final Context mContext;
+ private TileStateListener mListener;
+
+ public TileQueryHelper(Context context, QSTileHost host) {
+ mContext = context;
+ addSystemTiles(host);
+ // TODO: Live?
+ }
+
+ private void addSystemTiles(QSTileHost host) {
+ boolean hasColorMod = host.getDisplayController().isEnabled();
+ String possible = mContext.getString(R.string.quick_settings_tiles_default)
+ + ",hotspot,inversion,saver" + (hasColorMod ? ",colors" : "");
+ String[] possibleTiles = possible.split(",");
+ final Handler qsHandler = new Handler(host.getLooper());
+ final Handler mainHandler = new Handler(Looper.getMainLooper());
+ for (int i = 0; i < possibleTiles.length; i++) {
+ final String spec = possibleTiles[i];
+ final QSTile<?> tile = host.createTile(spec);
+ if (tile == null) {
+ continue;
+ }
+ tile.setListening(true);
+ tile.clearState();
+ tile.refreshState();
+ tile.setListening(false);
+ qsHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ final QSTile.State state = tile.newTileState();
+ tile.getState().copyTo(state);
+ mainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ addTile(spec, state, mTiles);
+ mListener.onTilesChanged(mTiles);
+ }
+ });
+ }
+ });
+ }
+ qsHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ new QueryTilesTask().execute();
+ }
+ });
+ }
+
+ public void setListener(TileStateListener listener) {
+ mListener = listener;
+ }
+
+ private static void addTile(String spec, QSTile.State state, List<TileInfo> tiles) {
+ TileInfo info = new TileInfo();
+ info.state = state;
+ info.spec = spec;
+ tiles.add(info);
+ }
+
+ private static void addTile(String spec, Drawable drawable, CharSequence label, Context context,
+ List<TileInfo> tiles) {
+ QSTile.State state = new QSTile.State();
+ state.label = label;
+ state.contentDescription = label;
+ state.icon = new DrawableIcon(drawable);
+ addTile(spec, state, tiles);
+ }
+
+ public static class TileInfo {
+ public String spec;
+ public QSTile.State state;
+ }
+
+ private class QueryTilesTask extends AsyncTask<Void, Void, Collection<TileInfo>> {
+ @Override
+ protected Collection<TileInfo> doInBackground(Void... params) {
+ List<TileInfo> tiles = new ArrayList<>();
+ PackageManager pm = mContext.getPackageManager();
+ List<ResolveInfo> services = pm.queryIntentServicesAsUser(
+ new Intent(TileService.ACTION_QS_TILE), 0, ActivityManager.getCurrentUser());
+ for (ResolveInfo info : services) {
+ String packageName = info.serviceInfo.packageName;
+ ComponentName componentName = new ComponentName(packageName, info.serviceInfo.name);
+ String spec = CustomTile.toSpec(componentName);
+ Drawable icon = info.serviceInfo.loadIcon(pm);
+ if (icon != null) {
+ icon.mutate();
+ icon.setTint(mContext.getColor(android.R.color.white));
+ }
+ CharSequence label = info.serviceInfo.loadLabel(pm);
+ addTile(spec, icon, label != null ? label.toString() : "null", mContext, tiles);
+ }
+ return tiles;
+ }
+
+ @Override
+ protected void onPostExecute(Collection<TileInfo> result) {
+ mTiles.addAll(result);
+ mListener.onTilesChanged(mTiles);
+ }
+ }
+
+ public interface TileStateListener {
+ void onTilesChanged(List<TileInfo> tiles);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index df3b5de..3cd9e67 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -151,7 +151,7 @@
}
@Override
- protected State newTileState() {
+ public State newTileState() {
return new State();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
index d78d6ff..5222e61 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -51,7 +51,7 @@
}
@Override
- protected BooleanState newTileState() {
+ public BooleanState newTileState() {
return new BooleanState();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
index 64b3a6c..cd3e3b2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
@@ -54,7 +54,7 @@
}
@Override
- protected State newTileState() {
+ public State newTileState() {
return new QSTile.State();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 874fc3e..1dce053 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -56,7 +56,7 @@
}
@Override
- protected BooleanState newTileState() {
+ public BooleanState newTileState() {
return new BooleanState();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 18eb7a1..15e082a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -60,7 +60,7 @@
}
@Override
- protected BooleanState newTileState() {
+ public BooleanState newTileState() {
return new BooleanState();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index aacdbc9..c3a2ebe 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -54,7 +54,7 @@
}
@Override
- protected SignalState newTileState() {
+ public SignalState newTileState() {
return new SignalState();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index 6e843e9..e98734c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -54,7 +54,7 @@
}
@Override
- protected BooleanState newTileState() {
+ public BooleanState newTileState() {
return new BooleanState();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index 1aeb0fe..c6a98b4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -30,7 +30,7 @@
}
@Override
- protected BooleanState newTileState() {
+ public BooleanState newTileState() {
return new BooleanState();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index f99a3e4..58872ec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -94,7 +94,7 @@
}
@Override
- protected BooleanState newTileState() {
+ public BooleanState newTileState() {
return new BooleanState();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
index 1d9f15b..f06634e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -47,7 +47,7 @@
}
@Override
- protected BooleanState newTileState() {
+ public BooleanState newTileState() {
return new BooleanState();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index 2f37943..943b502 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -44,7 +44,7 @@
}
@Override
- protected BooleanState newTileState() {
+ public BooleanState newTileState() {
return new BooleanState();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
index e1dc9f2..bdf95d8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
@@ -75,7 +75,7 @@
}
@Override
- protected State newTileState() {
+ public State newTileState() {
return new State();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index 8328897..9f41f9a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -45,7 +45,7 @@
}
@Override
- protected BooleanState newTileState() {
+ public BooleanState newTileState() {
return new BooleanState();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index f920d48..c94cf5a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -46,7 +46,7 @@
}
@Override
- protected BooleanState newTileState() {
+ public BooleanState newTileState() {
return new BooleanState();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
index 1565b6f..ba7ea4d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
@@ -37,7 +37,7 @@
}
@Override
- protected State newTileState() {
+ public State newTileState() {
return new QSTile.State();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index 42296f2..ac4dfd5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -60,7 +60,7 @@
}
@Override
- protected SignalState newTileState() {
+ public SignalState newTileState() {
return new SignalState();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index 508490f..a94973c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -53,7 +53,7 @@
}
@Override
- protected BooleanState newTileState() {
+ public BooleanState newTileState() {
return new BooleanState();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java b/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java
index d9276bf..3067714 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java
@@ -17,14 +17,12 @@
package com.android.systemui.statusbar;
import android.content.Context;
-import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import com.android.systemui.R;
public class DismissView extends StackScrollerDecorView {
- private boolean mDismissAllInProgress;
private DismissViewButton mDismissButton;
public DismissView(Context context, AttributeSet attrs) {
@@ -53,27 +51,7 @@
|| touchY > mContent.getY() + mContent.getHeight();
}
- public void showClearButton() {
- mDismissButton.showButton();
- }
-
- public void setDismissAllInProgress(boolean dismissAllInProgress) {
- if (dismissAllInProgress) {
- setClipBounds(null);
- }
- mDismissAllInProgress = dismissAllInProgress;
- }
-
- @Override
- public void setClipBounds(Rect clipBounds) {
- if (mDismissAllInProgress) {
- // we don't want any clipping to happen!
- return;
- }
- super.setClipBounds(clipBounds);
- }
-
public boolean isButtonVisible() {
- return mDismissButton.isButtonStatic();
+ return mDismissButton.getAlpha() != 0.0f;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java
index 46060f1..b608d67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java
@@ -17,22 +17,13 @@
package com.android.systemui.statusbar;
import android.content.Context;
-import android.graphics.Canvas;
import android.graphics.Rect;
-import android.graphics.drawable.AnimatedVectorDrawable;
-import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
-import android.view.View;
import android.view.ViewGroup;
-import android.widget.Button;
-import com.android.systemui.R;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
-public class DismissViewButton extends Button {
- private AnimatedVectorDrawable mAnimatedDismissDrawable;
- private final Drawable mStaticDismissDrawable;
- private Drawable mActiveDrawable;
+public class DismissViewButton extends AlphaOptimizedButton {
public DismissViewButton(Context context) {
this(context, null);
@@ -49,55 +40,6 @@
public DismissViewButton(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- mAnimatedDismissDrawable = (AnimatedVectorDrawable) getContext().getDrawable(
- R.drawable.dismiss_all_shape_animation).mutate();
- mAnimatedDismissDrawable.setCallback(this);
- mAnimatedDismissDrawable.setBounds(0,
- 0,
- mAnimatedDismissDrawable.getIntrinsicWidth(),
- mAnimatedDismissDrawable.getIntrinsicHeight());
- mStaticDismissDrawable = getContext().getDrawable(R.drawable.dismiss_all_shape);
- mStaticDismissDrawable.setBounds(0,
- 0,
- mStaticDismissDrawable.getIntrinsicWidth(),
- mStaticDismissDrawable.getIntrinsicHeight());
- mStaticDismissDrawable.setCallback(this);
- mActiveDrawable = mStaticDismissDrawable;
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- canvas.save();
- int drawableHeight = mActiveDrawable.getBounds().height();
- boolean isRtl = (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
- int dx = isRtl ? getWidth() / 2 + drawableHeight / 2 : getWidth() / 2 - drawableHeight / 2;
- canvas.translate(dx, getHeight() / 2.0f + drawableHeight /
- 2.0f);
- canvas.scale(isRtl ? -1.0f : 1.0f, -1.0f);
- mActiveDrawable.draw(canvas);
- canvas.restore();
- }
-
- @Override
- public boolean performClick() {
- if (!mAnimatedDismissDrawable.isRunning()) {
- mActiveDrawable = mAnimatedDismissDrawable;
- mAnimatedDismissDrawable.start();
- }
- return super.performClick();
- }
-
- @Override
- protected boolean verifyDrawable(Drawable who) {
- return super.verifyDrawable(who)
- || who == mAnimatedDismissDrawable
- || who == mStaticDismissDrawable;
- }
-
- @Override
- public boolean hasOverlappingRendering() {
- return false;
}
/**
@@ -118,16 +60,4 @@
outRect.top += translationY;
outRect.bottom += translationY;
}
-
- public void showButton() {
- mActiveDrawable = mStaticDismissDrawable;
- invalidate();
- }
-
- /**
- * @return Whether the button is currently static and not being animated.
- */
- public boolean isButtonStatic() {
- return mActiveDrawable == mStaticDismissDrawable;
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
index 98a37f9..c70aad2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
@@ -74,8 +74,7 @@
}
private void applyToChild(View view, boolean shouldApply, int originalColor) {
- if (view.getVisibility() == View.VISIBLE
- && originalColor != NotificationHeaderView.NO_COLOR) {
+ if (originalColor != NotificationHeaderView.NO_COLOR) {
ImageView imageView = (ImageView) view;
imageView.getDrawable().mutate();
if (shouldApply) {
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 ec73935..81144d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java
@@ -107,12 +107,13 @@
public void bind(CharSequence title, CharSequence text) {
mTitleView.setText(title);
- if (TextUtils.isEmpty(title)) {
- mTitleView.setVisibility(GONE);
- }
- mTextView.setText(text);
+ mTitleView.setVisibility(TextUtils.isEmpty(title) ? GONE : VISIBLE);
if (TextUtils.isEmpty(text)) {
mTextView.setVisibility(GONE);
+ mTextView.setText(null);
+ } else {
+ mTextView.setVisibility(VISIBLE);
+ mTextView.setText(text.toString());
}
requestLayout();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationViewManager.java
index 285d53f..28bb66f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationViewManager.java
@@ -18,8 +18,13 @@
import android.app.Notification;
import android.content.Context;
+import android.text.BidiFormatter;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
import android.text.TextUtils;
+import android.text.style.TextAppearanceSpan;
import android.view.LayoutInflater;
+import android.view.View;
import android.view.ViewGroup;
import com.android.systemui.R;
@@ -34,12 +39,12 @@
private final Context mContext;
private ViewGroup mParent;
- private String mConcadenationString;
+ private String mDivider;
public HybridNotificationViewManager(Context ctx, ViewGroup parent) {
mContext = ctx;
mParent = parent;
- mConcadenationString = mContext.getString(R.string.group_summary_concadenation);
+ mDivider = " • ";
}
private HybridNotificationView inflateHybridView() {
@@ -83,7 +88,7 @@
if (reusableView == null) {
reusableView = inflateHybridView();
}
- CharSequence summary = null;
+ SpannableStringBuilder summary = new SpannableStringBuilder();
int childCount = group.size();
for (int i = startIndex; i < childCount; i++) {
ExpandableNotificationRow child = group.get(i);
@@ -92,15 +97,18 @@
if (titleText == null) {
continue;
}
- if (TextUtils.isEmpty(summary)) {
- summary = titleText;
- } else if (reusableView.isLayoutRtl()) {
- summary = titleText + mConcadenationString + summary;
- } else {
- summary = summary + mConcadenationString + titleText;
+ 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));
}
- reusableView.bind(summary);
+ // 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/NotificationBigTextTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigTextTemplateViewWrapper.java
new file mode 100644
index 0000000..487a7a0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigTextTemplateViewWrapper.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import android.content.Context;
+import android.service.notification.StatusBarNotification;
+import android.view.View;
+
+import com.android.internal.widget.ImageFloatingTextView;
+import com.android.systemui.statusbar.TransformableView;
+
+/**
+ * Wraps a notification containing a big text template
+ */
+public class NotificationBigTextTemplateViewWrapper extends NotificationTemplateViewWrapper {
+
+ private ImageFloatingTextView mBigtext;
+
+ protected NotificationBigTextTemplateViewWrapper(Context ctx, View view) {
+ super(ctx, view);
+ }
+
+ private void resolveViews(StatusBarNotification notification) {
+ mBigtext = (ImageFloatingTextView) mView.findViewById(com.android.internal.R.id.big_text);
+ }
+
+ @Override
+ public void notifyContentUpdated(StatusBarNotification notification) {
+ // Reinspect the notification. Before the super call, because the super call also updates
+ // the transformation types and we need to have our values set by then.
+ resolveViews(notification);
+ super.notifyContentUpdated(notification);
+ }
+
+ @Override
+ protected void updateTransformedTypes() {
+ // This also clears the existing types
+ super.updateTransformedTypes();
+ if (mBigtext != null) {
+ mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TEXT,
+ mBigtext);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
index b060245..0c21f0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -55,11 +55,12 @@
}
TransformState otherState = notification.getCurrentState(
TRANSFORMING_VIEW_TITLE);
- CrossFadeHelper.fadeOut(mText, endRunnable);
+ final View text = ownState.getTransformedView();
+ CrossFadeHelper.fadeOut(text, endRunnable);
if (otherState != null) {
int[] otherStablePosition = otherState.getLaidOutLocationOnScreen();
int[] ownPosition = ownState.getLaidOutLocationOnScreen();
- mText.animate()
+ text.animate()
.translationY((otherStablePosition[1]
+ otherState.getTransformedView().getHeight()
- ownPosition[1]) * 0.33f)
@@ -72,11 +73,11 @@
if (endRunnable != null) {
endRunnable.run();
}
- TransformState.setClippingDeactivated(mText,
+ TransformState.setClippingDeactivated(text,
false);
}
});
- TransformState.setClippingDeactivated(mText, true);
+ TransformState.setClippingDeactivated(text, true);
otherState.recycle();
}
return true;
@@ -90,17 +91,18 @@
}
TransformState otherState = notification.getCurrentState(
TRANSFORMING_VIEW_TITLE);
- boolean isVisible = mText.getVisibility() == View.VISIBLE;
- CrossFadeHelper.fadeIn(mText);
+ final View text = ownState.getTransformedView();
+ boolean isVisible = text.getVisibility() == View.VISIBLE;
+ CrossFadeHelper.fadeIn(text);
if (otherState != null) {
int[] otherStablePosition = otherState.getLaidOutLocationOnScreen();
int[] ownStablePosition = ownState.getLaidOutLocationOnScreen();
if (!isVisible) {
- mText.setTranslationY((otherStablePosition[1]
+ text.setTranslationY((otherStablePosition[1]
+ otherState.getTransformedView().getHeight()
- ownStablePosition[1]) * 0.33f);
}
- mText.animate()
+ text.animate()
.translationY(0)
.setDuration(
StackStateAnimator.ANIMATION_DURATION_STANDARD)
@@ -108,11 +110,11 @@
.withEndAction(new Runnable() {
@Override
public void run() {
- TransformState.setClippingDeactivated(mText,
+ TransformState.setClippingDeactivated(text,
false);
}
});
- TransformState.setClippingDeactivated(mText, true);
+ TransformState.setClippingDeactivated(text, true);
otherState.recycle();
}
return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
index f50b976..a2b4c5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -37,6 +37,8 @@
if (v.getId() == com.android.internal.R.id.status_bar_latest_event_content) {
if ("bigPicture".equals(v.getTag())) {
return new NotificationBigPictureTemplateViewWrapper(ctx, v);
+ } else if ("bigText".equals(v.getTag())) {
+ return new NotificationBigTextTemplateViewWrapper(ctx, v);
}
return new NotificationTemplateViewWrapper(ctx, v);
} else if (v instanceof NotificationHeaderView) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BaseStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BaseStatusBarHeader.java
index 9a94d34..eade2a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BaseStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BaseStatusBarHeader.java
@@ -35,6 +35,7 @@
public abstract int getCollapsedHeight();
public abstract int getExpandedHeight();
+
public abstract void setExpanded(boolean b);
public abstract void setExpansion(float headerExpansionFraction);
public abstract void setListening(boolean listening);
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 3130eb9..d3681b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -154,10 +154,14 @@
}
mBarState = newState;
if (mBarState == StatusBarState.KEYGUARD) {
- for (NotificationGroup group : mGroupMap.values()) {
- if (group.expanded) {
- setGroupExpanded(group, false);
- }
+ collapseAllGroups();
+ }
+ }
+
+ public void collapseAllGroups() {
+ for (NotificationGroup group : mGroupMap.values()) {
+ if (group.expanded) {
+ setGroupExpanded(group, false);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
index cbb71c5..fd28b09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.phone;
import android.content.Context;
+import android.content.res.Configuration;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
@@ -55,6 +56,20 @@
}
@Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ reloadWidth(mScrollView);
+ reloadWidth(mStackScroller);
+ }
+
+ private void reloadWidth(View view) {
+ LayoutParams params = (LayoutParams) view.getLayoutParams();
+ params.width = getContext().getResources().getDimensionPixelSize(
+ R.dimen.notification_panel_width);
+ view.setLayoutParams(params);
+ }
+
+ @Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
setPadding(0, 0, 0, insets.getSystemWindowInsetBottom());
return insets;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
index 9aeb1f8..fe46385 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -31,7 +31,6 @@
import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
-
import com.android.keyguard.KeyguardStatusView;
import com.android.systemui.R;
import com.android.systemui.qs.QSPanel;
@@ -74,6 +73,9 @@
private float mDateTimeTranslation;
private MultiUserSwitch mMultiUserSwitch;
private ImageView mMultiUserAvatar;
+ private View mQsDetailHeaderBack;
+
+ private final int[] mTmpInt2 = new int[2];
public QuickStatusBarHeader(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -87,6 +89,7 @@
mDateTimeTranslation = mContext.getResources().getDimension(
R.dimen.qs_date_anim_translation);
mDateTimeGroup = (ViewGroup) findViewById(R.id.date_time_group);
+ mDateTimeGroup.findViewById(R.id.empty_time_view).setVisibility(View.GONE);
mExpandedGroup = (ViewGroup) findViewById(R.id.expanded_group);
mHeaderQsPanel = (QuickQSPanel) findViewById(R.id.quick_qs_panel);
@@ -100,6 +103,7 @@
mQsDetailHeader = findViewById(R.id.qs_detail_header);
mQsDetailHeader.setAlpha(0);
+ mQsDetailHeaderBack = mQsDetailHeader.findViewById(com.android.internal.R.id.up);
mQsDetailHeaderTitle = (TextView) mQsDetailHeader.findViewById(android.R.id.title);
mQsDetailHeaderSwitch = (Switch) mQsDetailHeader.findViewById(android.R.id.toggle);
mQsDetailHeaderProgress = (ImageView) findViewById(R.id.qs_detail_header_progress);
@@ -367,6 +371,15 @@
}
});
}
+ mQsDetailHeaderBack.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ v.getLocationInWindow(mTmpInt2);
+ mTmpInt2[0] += v.getWidth() / 2;
+ mTmpInt2[1] += v.getHeight() / 2;
+ mQsPanel.showDetailAdapter(false, null, mTmpInt2);
+ }
+ });
} else {
mQsDetailHeader.setClickable(false);
}
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 409dac1..5cfcd89 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.stack;
import android.content.Context;
+import android.content.res.Configuration;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -58,6 +59,7 @@
private HybridNotificationView mGroupOverflowContainer;
private ViewState mGroupOverFlowState;
private int mRealHeight;
+ private int mLayoutDirection = LAYOUT_DIRECTION_UNDEFINED;
public NotificationChildrenContainer(Context context) {
this(context, null);
@@ -209,6 +211,16 @@
}
}
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ int layoutDirection = getLayoutDirection();
+ if (layoutDirection != mLayoutDirection) {
+ updateGroupOverflow();
+ mLayoutDirection = layoutDirection;
+ }
+ }
+
private View inflateDivider() {
return LayoutInflater.from(mContext).inflate(
R.layout.notification_children_divider, this, false);
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 d6276b8..cc0e67d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -1648,6 +1648,9 @@
bottom = (int) (lastView.getTranslationY() + lastView.getActualHeight());
bottom = Math.min(bottom, getHeight());
}
+ } else if (mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD) {
+ top = mTopPadding;
+ bottom = top;
}
mBackgroundBounds.top = Math.max(0, top);
mBackgroundBounds.bottom = Math.min(getHeight(), bottom);
@@ -2093,12 +2096,6 @@
generateAddAnimation(child, false /* fromMoreCard */);
updateAnimationState(child);
updateChronometerForChild(child);
- if (canChildBeDismissed(child)) {
- // Make sure the dismissButton is visible and not in the animated state.
- // We need to do this to avoid a race where a clearable notification is added after the
- // dismiss animation is finished
- mDismissView.showClearButton();
- }
}
private void updateHideSensitiveForChild(View child) {
@@ -2608,6 +2605,9 @@
mIsExpanded = isExpanded;
mStackScrollAlgorithm.setIsExpanded(isExpanded);
if (changed) {
+ if (!mIsExpanded) {
+ mGroupManager.collapseAllGroups();
+ }
updateNotificationAnimationStates();
updateChronometers();
}
@@ -2977,7 +2977,6 @@
mDismissView.performVisibilityAnimation(false, dimissHideFinishRunnable);
} else {
dimissHideFinishRunnable.run();
- mDismissView.showClearButton();
}
}
}
@@ -2985,7 +2984,6 @@
public void setDismissAllInProgress(boolean dismissAllInProgress) {
mDismissAllInProgress = dismissAllInProgress;
- mDismissView.setDismissAllInProgress(dismissAllInProgress);
mAmbientState.setDismissAllInProgress(dismissAllInProgress);
if (dismissAllInProgress) {
disableClipOptimization();
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/ColorMatrixTile.java b/packages/SystemUI/src/com/android/systemui/tuner/ColorMatrixTile.java
index 7b06393..d8cf2e2 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/ColorMatrixTile.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/ColorMatrixTile.java
@@ -56,7 +56,7 @@
}
@Override
- protected State newTileState() {
+ public State newTileState() {
return new State();
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java
index 912e8e2..b79a7ba 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java
@@ -25,6 +25,7 @@
import android.gesture.GestureStroke;
import android.gesture.Prediction;
import android.util.Slog;
+import android.util.TypedValue;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.VelocityTracker;
@@ -70,7 +71,7 @@
// Indicates that we've collected enough data to be sure it could be a
// gesture.
- private boolean mGestureConfirmed;
+ private boolean mGestureStarted;
// Indicates that motion events from the second pointer are being checked
// for a double tap.
@@ -83,47 +84,39 @@
// Policy flags of the previous event.
private int mPolicyFlags;
- // The X of the previous event.
- private float mPreviousX;
+ // These values track the previous point that was saved to use for gesture
+ // detection. They are only updated when the user moves more than the
+ // recognition threshold.
+ private float mPreviousGestureX;
+ private float mPreviousGestureY;
- // The Y of the previous event.
- private float mPreviousY;
-
- // The X of the down event.
+ // These values track the previous point that was used to determine if there
+ // was a transition into or out of gesture detection. They are updated when
+ // the user moves more than the detection threshold.
private float mBaseX;
-
- // The Y of the down event.
private float mBaseY;
+ private long mBaseTime;
- // Slop between the first and second tap to be a double tap.
- private final int mDoubleTapSlop;
-
- // The scaled velocity above which we detect gestures.
- private final int mScaledGestureDetectionVelocity;
+ // This is the calculated movement threshold used track if the user is still
+ // moving their finger.
+ private final float mGestureDetectionThreshold;
// Buffer for storing points for gesture detection.
private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
- // Helper to track gesture velocity.
- private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
-
// The minimal delta between moves to add a gesture point.
private static final int TOUCH_TOLERANCE = 3;
// The minimal score for accepting a predicted gesture.
private static final float MIN_PREDICTION_SCORE = 2.0f;
- // The velocity above which we detect gestures. Expressed in DIPs/Second.
- private static final int GESTURE_DETECTION_VELOCITY_DIP = 1000;
-
- // Constant used to calculate velocity in seconds.
- private static final int VELOCITY_UNITS_SECONDS = 1000;
+ private static final int GESTURE_CONFIRM_MM = 10;
+ private static final long CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS = 1000;
+ private static final long CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS = 500;
AccessibilityGestureDetector(Context context, Listener listener) {
mListener = listener;
- mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
-
mGestureDetector = new GestureDetector(context, this);
mGestureDetector.setOnDoubleTapListener(this);
@@ -132,15 +125,14 @@
mGestureLibrary.setSequenceType(GestureStore.SEQUENCE_SENSITIVE);
mGestureLibrary.load();
- final float density = context.getResources().getDisplayMetrics().density;
- mScaledGestureDetectionVelocity = (int) (GESTURE_DETECTION_VELOCITY_DIP * density);
+ mGestureDetectionThreshold = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1,
+ context.getResources().getDisplayMetrics()) * GESTURE_CONFIRM_MM;
}
public boolean onMotionEvent(MotionEvent event, int policyFlags) {
- mVelocityTracker.addMovement(event);
-
final float x = event.getX();
final float y = event.getY();
+ final long time = event.getEventTime();
mPolicyFlags = policyFlags;
switch (event.getActionMasked()) {
@@ -148,54 +140,56 @@
mDoubleTapDetected = false;
mSecondFingerDoubleTap = false;
mRecognizingGesture = true;
- mGestureConfirmed = false;
+ mGestureStarted = false;
+ mPreviousGestureX = x;
+ mPreviousGestureY = y;
+ mStrokeBuffer.clear();
+ mStrokeBuffer.add(new GesturePoint(x, y, time));
+
mBaseX = x;
mBaseY = y;
- mPreviousX = x;
- mPreviousY = y;
- mStrokeBuffer.clear();
- mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
- mVelocityTracker.clear();
- mVelocityTracker.addMovement(event);
+ mBaseTime = time;
break;
case MotionEvent.ACTION_MOVE:
if (mRecognizingGesture) {
- if (!mGestureConfirmed) {
- mVelocityTracker.addMovement(event);
- // It is *important* to use the distance traveled by the pointers
- // on the screen which may or may not be magnified.
- final float deltaX = mBaseX - event.getX(0);
- final float deltaY = mBaseY - event.getY(0);
- final double moveDelta = Math.hypot(deltaX, deltaY);
- // The user has moved enough for us to decide.
- if (moveDelta > mDoubleTapSlop) {
- // Check whether the user is performing a gesture. We
- // detect gestures if the pointer is moving above a
- // given velocity.
- mVelocityTracker.computeCurrentVelocity(VELOCITY_UNITS_SECONDS);
- final float maxAbsVelocity = Math.max(
- Math.abs(mVelocityTracker.getXVelocity(0)),
- Math.abs(mVelocityTracker.getYVelocity(0)));
- if (maxAbsVelocity > mScaledGestureDetectionVelocity) {
- // We have to perform gesture detection, so
- // notify the listener.
- mGestureConfirmed = true;
- mListener.onGestureStarted();
- } else {
- // This won't match any gesture, so notify the
- // listener.
- cancelGesture();
- mListener.onGestureCancelled(event, policyFlags);
- }
+ final float deltaX = mBaseX - x;
+ final float deltaY = mBaseY - y;
+ final double moveDelta = Math.hypot(deltaX, deltaY);
+ if (moveDelta > mGestureDetectionThreshold) {
+ // If the pointer has moved more than the threshold,
+ // update the stored values.
+ mBaseX = x;
+ mBaseY = y;
+ mBaseTime = time;
+
+ // If this hasn't been confirmed as a gesture yet, send
+ // the event.
+ if (!mGestureStarted) {
+ mGestureStarted = true;
+ mListener.onGestureStarted();
+ }
+ } else {
+ final long timeDelta = time - mBaseTime;
+ final long threshold = mGestureStarted ?
+ CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS :
+ CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS;
+
+ // If the pointer hasn't moved for longer than the
+ // timeout, cancel gesture detection.
+ if (timeDelta > threshold) {
+ cancelGesture();
+ mListener.onGestureCancelled(event, policyFlags);
+ return false;
}
}
- final float dX = Math.abs(x - mPreviousX);
- final float dY = Math.abs(y - mPreviousY);
+
+ final float dX = Math.abs(x - mPreviousGestureX);
+ final float dY = Math.abs(y - mPreviousGestureY);
if (dX >= TOUCH_TOLERANCE || dY >= TOUCH_TOLERANCE) {
- mPreviousX = x;
- mPreviousY = y;
- mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
+ mPreviousGestureX = x;
+ mPreviousGestureY = y;
+ mStrokeBuffer.add(new GesturePoint(x, y, time));
}
}
break;
@@ -204,8 +198,8 @@
if (maybeFinishDoubleTap(event, policyFlags)) {
return true;
}
- if (mGestureConfirmed) {
- mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
+ if (mGestureStarted) {
+ mStrokeBuffer.add(new GesturePoint(x, y, time));
if (!recognizeGesture()) {
mListener.onGestureCancelled(event, policyFlags);
@@ -223,7 +217,7 @@
// If this was the second finger, attempt to recognize double
// taps on it.
mSecondFingerDoubleTap = true;
- mSecondPointerDownTime = event.getEventTime();
+ mSecondPointerDownTime = time;
} else {
// If there are more than two fingers down, stop watching
// for a double tap.
@@ -268,8 +262,8 @@
mFirstTapDetected = false;
mDoubleTapDetected = false;
mSecondFingerDoubleTap = false;
+ mGestureStarted = false;
cancelGesture();
- mVelocityTracker.clear();
}
public boolean firstTapDetected() {
@@ -323,7 +317,7 @@
private void cancelGesture() {
mRecognizingGesture = false;
- mGestureConfirmed = false;
+ mGestureStarted = false;
mStrokeBuffer.clear();
}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index c535ebd..5e948b1 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -2166,7 +2166,8 @@
try {
ApplicationInfo app = pkg.applicationInfo;
if (((app.flags&ApplicationInfo.FLAG_ALLOW_BACKUP) == 0)
- || app.backupAgentName == null) {
+ || app.backupAgentName == null
+ || (app.flags&ApplicationInfo.FLAG_FULL_BACKUP_ONLY) != 0) {
packages.remove(a);
}
else {
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 5f57a76..4667172 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -111,8 +111,9 @@
static final boolean WAKEUP_STATS = false;
- private static final Intent NEXT_ALARM_CLOCK_CHANGED_INTENT = new Intent(
- AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
+ private static final Intent NEXT_ALARM_CLOCK_CHANGED_INTENT =
+ new Intent(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)
+ .addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
final LocalLog mLog = new LocalLog(TAG);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 54c4ced..1506524 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5303,28 +5303,42 @@
@Override
public void killAllBackgroundProcesses() {
+ killAllBackgroundProcesses(-1);
+ }
+
+ /**
+ * Kills all background processes with targetSdkVersion below the specified
+ * target SDK version.
+ *
+ * @param targetSdkVersion the target SDK version below which to kill
+ * processes, or {@code -1} to kill all processes
+ */
+ private void killAllBackgroundProcesses(int targetSdkVersion) {
if (checkCallingPermission(android.Manifest.permission.KILL_BACKGROUND_PROCESSES)
!= PackageManager.PERMISSION_GRANTED) {
- String msg = "Permission Denial: killAllBackgroundProcesses() from pid="
- + Binder.getCallingPid()
- + ", uid=" + Binder.getCallingUid()
+ final String msg = "Permission Denial: killAllBackgroundProcesses() from pid="
+ + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+ " requires " + android.Manifest.permission.KILL_BACKGROUND_PROCESSES;
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
- long callingId = Binder.clearCallingIdentity();
+ final long callingId = Binder.clearCallingIdentity();
try {
- synchronized(this) {
- ArrayList<ProcessRecord> procs = new ArrayList<ProcessRecord>();
+ synchronized (this) {
+ final ArrayList<ProcessRecord> procs = new ArrayList<>();
final int NP = mProcessNames.getMap().size();
- for (int ip=0; ip<NP; ip++) {
- SparseArray<ProcessRecord> apps = mProcessNames.getMap().valueAt(ip);
+ for (int ip = 0; ip < NP; ip++) {
+ final SparseArray<ProcessRecord> apps = mProcessNames.getMap().valueAt(ip);
final int NA = apps.size();
- for (int ia=0; ia<NA; ia++) {
- ProcessRecord app = apps.valueAt(ia);
+ for (int ia = 0; ia < NA; ia++) {
+ final ProcessRecord app = apps.valueAt(ia);
if (app.persistent) {
- // we don't kill persistent processes
+ // We don't kill persistent processes.
+ continue;
+ }
+ if (targetSdkVersion > 0
+ && app.info.targetSdkVersion >= targetSdkVersion) {
continue;
}
if (app.removed) {
@@ -5336,11 +5350,13 @@
}
}
- int N = procs.size();
- for (int i=0; i<N; i++) {
+ final int N = procs.size();
+ for (int i = 0; i < N; i++) {
removeProcessLocked(procs.get(i), false, true, "kill all background");
}
+
mAllowLowerMemLevel = true;
+
updateOomAdjLocked();
doLowMemReportIfNeededLocked(null);
}
@@ -17805,6 +17821,11 @@
mHandler.sendMessage(msg);
}
+ final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0;
+ if (isDensityChange) {
+ killAllBackgroundProcesses(Build.VERSION_CODES.N);
+ }
+
for (int i=mLruProcesses.size()-1; i>=0; i--) {
ProcessRecord app = mLruProcesses.get(i);
try {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 64c0891..548f563 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3889,8 +3889,29 @@
int index;
if (mIsMuted) {
index = 0;
- } else if (((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && mAvrcpAbsVolSupported)
- || ((device & mFullVolumeDevices) != 0)) {
+ } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && mAvrcpAbsVolSupported) {
+ /* Special handling for Bluetooth Absolute Volume scenario
+ * If we send full audio gain, some accessories are too loud even at its lowest
+ * volume. We are not able to enumerate all such accessories, so here is the
+ * workaround from phone side.
+ * For the lowest volume steps 1 and 2, restrict audio gain to 50% and 75%.
+ * For volume step 0, set audio gain to 0 as some accessories won't mute on their end.
+ */
+ int i = (getIndex(device) + 5)/10;
+ if (i == 0) {
+ // 0% for volume 0
+ index = 0;
+ } else if (i == 1) {
+ // 50% for volume 1
+ index = (int)(mIndexMax * 0.5) /10;
+ } else if (i == 2) {
+ // 75% for volume 2
+ index = (int)(mIndexMax * 0.75) /10;
+ } else {
+ // otherwise, full gain
+ index = (mIndexMax + 5)/10;
+ }
+ } else if ((device & mFullVolumeDevices) != 0) {
index = (mIndexMax + 5)/10;
} else {
index = (getIndex(device) + 5)/10;
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 544f255..7f8099e 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -192,7 +192,7 @@
* enforcement.
*/
public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
- private static final String TAG = "NetworkPolicy";
+ static final String TAG = "NetworkPolicy";
private static final boolean LOGD = false;
private static final boolean LOGV = false;
@@ -1689,6 +1689,7 @@
}
writePolicy = true;
}
+ updateRulesForGlobalChangeLocked(true);
// Remove associated UID policies
int[] uids = new int[0];
@@ -1862,8 +1863,8 @@
Slog.i(TAG, "adding uid " + uid + " to restrict background whitelist");
synchronized (mRulesLock) {
mRestrictBackgroundWhitelistUids.append(uid, true);
+ updateRulesForGlobalChangeLocked(true);
writePolicyLocked();
- // TODO: call other update methods like updateNetworkRulesLocked?
}
mHandler.obtainMessage(MSG_RESTRICT_BACKGROUND_WHITELIST_CHANGED, uid, 0).sendToTarget();
}
@@ -1878,9 +1879,10 @@
mHandler.obtainMessage(MSG_RESTRICT_BACKGROUND_WHITELIST_CHANGED, uid, 0).sendToTarget();
}
- private void removeRestrictBackgroundWhitelistedUidLocked(int uid, boolean writePolicy) {
+ private void removeRestrictBackgroundWhitelistedUidLocked(int uid, boolean updateNow) {
mRestrictBackgroundWhitelistUids.delete(uid);
- if (writePolicy) {
+ if (updateNow) {
+ updateRulesForGlobalChangeLocked(true);
writePolicyLocked();
}
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java b/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java
index 5830b0e..281c3d0 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java
@@ -16,19 +16,24 @@
package com.android.server.net;
-import java.io.PrintWriter;
+import static com.android.server.net.NetworkPolicyManagerService.TAG;
-import android.content.Intent;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
import android.net.INetworkPolicyManager;
+import android.net.NetworkPolicy;
import android.os.Binder;
import android.os.RemoteException;
import android.os.ShellCommand;
+import android.util.Log;
-public class NetworkPolicyManagerShellCommand extends ShellCommand {
+class NetworkPolicyManagerShellCommand extends ShellCommand {
final INetworkPolicyManager mInterface;
- NetworkPolicyManagerShellCommand(NetworkPolicyManagerService service) {
+ NetworkPolicyManagerShellCommand(INetworkPolicyManager service) {
mInterface = service;
}
@@ -66,16 +71,24 @@
pw.println(" help");
pw.println(" Print this help text.");
pw.println("");
- pw.println(" get restrict-background");
- pw.println(" Gets the global restrict background usage status.");
- pw.println(" set restrict-background BOOLEAN");
- pw.println(" Sets the global restrict background usage status.");
- pw.println(" list restrict-background-whitelist");
- pw.println(" Prints UID that are whitelisted for restrict background usage.");
pw.println(" add restrict-background-whitelist UID");
pw.println(" Adds a UID to the whitelist for restrict background usage.");
+ pw.println(" get metered-network ID");
+ pw.println(" Checks whether the given non-mobile network is metered or not.");
+ pw.println(" get restrict-background");
+ pw.println(" Gets the global restrict background usage status.");
+ pw.println(" list metered-networks [BOOLEAN]");
+ pw.println(" Lists all non-mobile networks and whether they are metered or not.");
+ pw.println(" If a boolean argument is passed, filters just the metered (or unmetered)");
+ pw.println(" networks.");
+ pw.println(" list restrict-background-whitelist");
+ pw.println(" Lists UIDs that are whitelisted for restrict background usage.");
pw.println(" remove restrict-background-whitelist UID");
pw.println(" Removes a UID from the whitelist for restrict background usage.");
+ pw.println(" set metered-network ID BOOLEAN");
+ pw.println(" Toggles whether the given non-mobile network is metered.");
+ pw.println(" set restrict-background BOOLEAN");
+ pw.println(" Sets the global restrict background usage status.");
}
private int runGet() throws RemoteException {
@@ -86,6 +99,8 @@
return -1;
}
switch(type) {
+ case "metered-network":
+ return getNonMobileMeteredNetwork();
case "restrict-background":
return getRestrictBackground();
}
@@ -101,6 +116,8 @@
return -1;
}
switch(type) {
+ case "metered-network":
+ return setNonMobileMeteredNetwork();
case "restrict-background":
return setRestrictBackground();
}
@@ -116,6 +133,8 @@
return -1;
}
switch(type) {
+ case "metered-networks":
+ return listNonMobileMeteredNetworks();
case "restrict-background-whitelist":
return runListRestrictBackgroundWhitelist();
}
@@ -196,7 +215,12 @@
if (uid < 0) {
return uid;
}
- mInterface.addRestrictBackgroundWhitelistedUid(uid);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mInterface.addRestrictBackgroundWhitelistedUid(uid);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
return 0;
}
@@ -205,10 +229,95 @@
if (uid < 0) {
return uid;
}
- mInterface.removeRestrictBackgroundWhitelistedUid(uid);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mInterface.removeRestrictBackgroundWhitelistedUid(uid);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
return 0;
}
+ private int listNonMobileMeteredNetworks() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ final String arg = getNextArg();
+ final Boolean filter = arg == null ? null : Boolean.valueOf(arg);
+ for (NetworkPolicy policy : getNonMobilePolicies()) {
+ if (filter != null && filter.booleanValue() != policy.metered) {
+ continue;
+ }
+ pw.print(getNetworkId(policy));
+ pw.print(';');
+ pw.println(policy.metered);
+ }
+ return 0;
+ }
+
+ private int getNonMobileMeteredNetwork() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ final String id = getNextArg();
+ if (id == null) {
+ pw.println("Error: didn't specify ID");
+ return -1;
+ }
+ final List<NetworkPolicy> policies = getNonMobilePolicies();
+ for (NetworkPolicy policy: policies) {
+ if (id.equals(getNetworkId(policy))) {
+ pw.println(policy.metered);
+ return 0;
+ }
+ }
+ return 0;
+ }
+
+ private int setNonMobileMeteredNetwork() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ final String id = getNextArg();
+ if (id == null) {
+ pw.println("Error: didn't specify ID");
+ return -1;
+ }
+ final String arg = getNextArg();
+ if (arg == null) {
+ pw.println("Error: didn't specify BOOLEAN");
+ return -1;
+ }
+ final boolean metered = Boolean.valueOf(arg);
+ final NetworkPolicy[] policies = mInterface.getNetworkPolicies(null);
+ boolean changed = false;
+ for (NetworkPolicy policy : policies) {
+ if (policy.template.isMatchRuleMobile() || policy.metered == metered) {
+ continue;
+ }
+ final String networkId = getNetworkId(policy);
+ if (id.equals(networkId)) {
+ Log.i(TAG, "Changing " + networkId + " metered status to " + metered);
+ policy.metered = metered;
+ changed = true;
+ }
+ }
+ if (changed) {
+ mInterface.setNetworkPolicies(policies);
+ }
+ return 0;
+ }
+
+ private List<NetworkPolicy> getNonMobilePolicies() throws RemoteException {
+ final NetworkPolicy[] policies = mInterface.getNetworkPolicies(null);
+ final List<NetworkPolicy> nonMobilePolicies = new ArrayList<NetworkPolicy>(policies.length);
+ for (NetworkPolicy policy: policies) {
+ if (!policy.template.isMatchRuleMobile()) {
+ nonMobilePolicies.add(policy);
+ }
+ }
+ return nonMobilePolicies;
+ }
+
+ private String getNetworkId(NetworkPolicy policy) {
+ // ids are typically enclosed on double quotes (")
+ return policy.template.getNetworkId().replaceAll("^\"|\"$", "");
+ }
+
private int getNextBooleanArg() {
final PrintWriter pw = getOutPrintWriter();
final String arg = getNextArg();
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 1d91fb7..b7abce21 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -19,6 +19,8 @@
import static android.media.AudioAttributes.USAGE_ALARM;
import static android.media.AudioAttributes.USAGE_NOTIFICATION;
import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
+import static android.media.AudioAttributes.USAGE_UNKNOWN;
+import static android.media.AudioAttributes.USAGE_VIRTUAL_SOURCE;
import android.app.AppOpsManager;
import android.app.AutomaticZenRule;
@@ -34,6 +36,7 @@
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.database.ContentObserver;
+import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioManagerInternal;
import android.media.AudioSystem;
@@ -705,15 +708,20 @@
// notification restrictions
final boolean muteNotifications = mEffectsSuppressed;
- applyRestrictions(muteNotifications, USAGE_NOTIFICATION);
-
// call restrictions
final boolean muteCalls = zen && !mConfig.allowCalls && !mConfig.allowRepeatCallers;
- applyRestrictions(muteCalls, USAGE_NOTIFICATION_RINGTONE);
+ // total silence restrictions
+ final boolean muteEverything = mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS;
- // alarm restrictions
- final boolean muteAlarms = mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS;
- applyRestrictions(muteAlarms, USAGE_ALARM);
+ for (int i = USAGE_UNKNOWN; i <= USAGE_VIRTUAL_SOURCE; i++) {
+ if (i == USAGE_NOTIFICATION) {
+ applyRestrictions(muteNotifications || muteEverything, i);
+ } else if (i == USAGE_NOTIFICATION_RINGTONE) {
+ applyRestrictions(muteCalls || muteEverything, i);
+ } else {
+ applyRestrictions(muteEverything, i);
+ }
+ }
}
private void applyRestrictions(boolean mute, int usage) {
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index 2deb0d5..2f076d1 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -36,6 +36,9 @@
public static final String TAG = "VrManagerService";
+ private static native void initializeNative();
+ private static native void setVrModeNative(boolean enabled);
+
private final Object mLock = new Object();
private boolean mVrModeEnabled = false;
private ArraySet<VrStateListener> mListeners = new ArraySet<>();
@@ -68,6 +71,10 @@
@Override
public void onStart() {
+ synchronized(mLock) {
+ initializeNative();
+ }
+
publishLocalService(VrManagerInternal.class, new LocalService());
}
@@ -89,6 +96,7 @@
mVrModeEnabled = enabled;
// Log mode change event.
Slog.i(TAG, "VR mode " + ((mVrModeEnabled) ? "enabled" : "disabled"));
+ setVrModeNative(mVrModeEnabled);
onVrModeChangedLocked();
}
}
diff --git a/services/core/java/com/android/server/wm/BoundsAnimationController.java b/services/core/java/com/android/server/wm/BoundsAnimationController.java
index 5f97478..1bfdcce 100644
--- a/services/core/java/com/android/server/wm/BoundsAnimationController.java
+++ b/services/core/java/com/android/server/wm/BoundsAnimationController.java
@@ -22,6 +22,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.graphics.Rect;
import android.util.ArrayMap;
@@ -50,13 +51,15 @@
private final Rect mFrom;
private final Rect mTo;
private final Rect mTmpRect;
+ private final boolean mMoveToFullScreen;
- BoundsAnimator(AnimateBoundsUser target, Rect from, Rect to) {
+ BoundsAnimator(AnimateBoundsUser target, Rect from, Rect to, boolean moveToFullScreen) {
super();
mTarget = target;
mFrom = from;
mTo = to;
mTmpRect = new Rect();
+ mMoveToFullScreen = moveToFullScreen;
addUpdateListener(this);
addListener(this);
}
@@ -88,6 +91,9 @@
@Override
public void onAnimationEnd(Animator animation) {
finishAnimation();
+ if (mMoveToFullScreen) {
+ mTarget.moveToFullscreen();
+ }
}
@Override
@@ -125,14 +131,25 @@
* necessary cleanup.
*/
void finishBoundsAnimation();
+
+ void moveToFullscreen();
+
+ void getFullScreenBounds(Rect bounds);
}
- void animateBounds(AnimateBoundsUser target, Rect from, Rect to) {
+ void animateBounds(final AnimateBoundsUser target, Rect from, Rect to) {
+ boolean moveToFullscreen = false;
+ if (to == null) {
+ to = new Rect();
+ target.getFullScreenBounds(to);
+ moveToFullscreen = true;
+ }
+
final BoundsAnimator existing = mRunningAnimations.get(target);
if (existing != null) {
existing.cancel();
}
- BoundsAnimator animator = new BoundsAnimator(target, from, to);
+ BoundsAnimator animator = new BoundsAnimator(target, from, to, moveToFullscreen);
mRunningAnimations.put(target, animator);
animator.setFloatValues(0f, 1f);
animator.setDuration(DEFAULT_APP_TRANSITION_DURATION);
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 4659131..ecc1364 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -927,4 +927,18 @@
}
}
}
+
+ @Override
+ public void moveToFullscreen() {
+ try {
+ mService.mActivityManager.moveTasksToFullscreenStack(mStackId, true);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void getFullScreenBounds(Rect bounds) {
+ getDisplayContent().getContentRect(bounds);
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowLayersController.java b/services/core/java/com/android/server/wm/WindowLayersController.java
index 2cf2618..d843a8c 100644
--- a/services/core/java/com/android/server/wm/WindowLayersController.java
+++ b/services/core/java/com/android/server/wm/WindowLayersController.java
@@ -16,12 +16,14 @@
package com.android.server.wm;
-import android.app.ActivityManager.StackId;
import android.util.Slog;
import android.view.Display;
import java.io.PrintWriter;
+import java.util.ArrayDeque;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -55,10 +57,10 @@
}
private int mHighestApplicationLayer = 0;
- private WindowState mPinnedWindow = null;
- private WindowState mDockedWindow = null;
+ private ArrayDeque<WindowState> mPinnedWindows = new ArrayDeque<>();
+ private ArrayDeque<WindowState> mDockedWindows = new ArrayDeque<>();
private WindowState mDockDivider = null;
- private WindowState mReplacingWindow = null;
+ private ArrayDeque<WindowState> mReplacingWindows = new ArrayDeque<>();
final void assignLayersLocked(WindowList windows) {
if (DEBUG_LAYERS) Slog.v(TAG_WM, "Assigning layers based on windows=" + windows,
@@ -169,43 +171,54 @@
private void clear() {
mHighestApplicationLayer = 0;
- mPinnedWindow = null;
- mDockedWindow = null;
+ mPinnedWindows.clear();
+ mDockedWindows.clear();
+ mReplacingWindows.clear();
mDockDivider = null;
}
private void collectSpecialWindows(WindowState w) {
if (w.mAttrs.type == TYPE_DOCK_DIVIDER) {
mDockDivider = w;
- } else {
- final TaskStack stack = w.getStack();
- if (stack == null) {
- return;
- }
- if (stack.mStackId == StackId.PINNED_STACK_ID) {
- mPinnedWindow = w;
- } else if (stack.mStackId == StackId.DOCKED_STACK_ID) {
- mDockedWindow = w;
- }
+ return;
+ }
+ if (w.mWillReplaceWindow) {
+ mReplacingWindows.add(w);
+ }
+ final TaskStack stack = w.getStack();
+ if (stack == null) {
+ return;
+ }
+ if (stack.mStackId == PINNED_STACK_ID) {
+ mPinnedWindows.add(w);
+ } else if (stack.mStackId == DOCKED_STACK_ID) {
+ mDockedWindows.add(w);
}
}
private void adjustSpecialWindows() {
int layer = mHighestApplicationLayer + 1;
- // For pinned and docked stack window, we want to make them above other windows
- // also when these windows are animating.
- layer = assignAndIncreaseLayerIfNeeded(mDockedWindow, layer);
+ // For pinned and docked stack window, we want to make them above other windows also when
+ // these windows are animating.
+ while (!mDockedWindows.isEmpty()) {
+ layer = assignAndIncreaseLayerIfNeeded(mDockedWindows.remove(), layer);
+ }
// Leave some space here so the dim layer while dismissing docked/fullscreen stack has space
// below the divider but above the app windows. It needs to be below the divider in because
// the divider sometimes overlaps the app windows.
layer++;
layer = assignAndIncreaseLayerIfNeeded(mDockDivider, layer);
- // We know that we will be animating a relaunching window in the near future,
- // which will receive a z-order increase. We want the replaced window to
- // immediately receive the same treatment, e.g. to be above the dock divider.
- layer = assignAndIncreaseLayerIfNeeded(mReplacingWindow, layer);
- layer = assignAndIncreaseLayerIfNeeded(mPinnedWindow, layer);
+ // We know that we will be animating a relaunching window in the near future, which will
+ // receive a z-order increase. We want the replaced window to immediately receive the same
+ // treatment, e.g. to be above the dock divider.
+ while (!mReplacingWindows.isEmpty()) {
+ layer = assignAndIncreaseLayerIfNeeded(mReplacingWindows.remove(), layer);
+ }
+
+ while (!mPinnedWindows.isEmpty()) {
+ layer = assignAndIncreaseLayerIfNeeded(mPinnedWindows.remove(), layer);
+ }
}
private int assignAndIncreaseLayerIfNeeded(WindowState win, int layer) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
index 66aa863..0979cd3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
+++ b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
@@ -35,10 +35,10 @@
static final boolean DEBUG_RESIZE = false;
static final boolean DEBUG = false;
- static final boolean DEBUG_ADD_REMOVE = true;
+ static final boolean DEBUG_ADD_REMOVE = false;
static final boolean DEBUG_FOCUS = false;
static final boolean DEBUG_FOCUS_LIGHT = DEBUG_FOCUS || false;
- static final boolean DEBUG_ANIM = true;
+ static final boolean DEBUG_ANIM = false;
static final boolean DEBUG_KEYGUARD = false;
static final boolean DEBUG_LAYOUT = false;
static final boolean DEBUG_LAYERS = false;
@@ -50,7 +50,7 @@
static final boolean DEBUG_ORIENTATION = false;
static final boolean DEBUG_APP_ORIENTATION = false;
static final boolean DEBUG_CONFIGURATION = false;
- static final boolean DEBUG_APP_TRANSITIONS = true;
+ static final boolean DEBUG_APP_TRANSITIONS = false;
static final boolean DEBUG_STARTING_WINDOW = false;
static final boolean DEBUG_WALLPAPER = false;
static final boolean DEBUG_WALLPAPER_LIGHT = false || DEBUG_WALLPAPER;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9f833d1..a26430e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2856,6 +2856,9 @@
WindowStateAnimator winAnimator, int attrChanges, int oldVisibility) {
result |= !win.isVisibleLw() ? WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME : 0;
if (win.mExiting) {
+ Slog.d(TAG, "relayoutVisibleWindow: " + win + " mExiting=true, mRemoveOnExit="
+ + win.mRemoveOnExit + ", mDestroying=" + win.mDestroying);
+
winAnimator.cancelExitAnimationForNextAnimationLocked();
win.mExiting = false;
}
@@ -8106,7 +8109,9 @@
final WindowState imeWin = mInputMethodWindow;
final TaskStack focusedStack =
mCurrentFocus != null ? mCurrentFocus.getStack() : null;
- if (imeWin != null && focusedStack != null && imeWin.isVisibleNow()
+ if (imeWin != null && imeWin.isVisibleNow()
+ && isStackVisibleLocked(DOCKED_STACK_ID)
+ && focusedStack != null
&& focusedStack.getDockSide() == DOCKED_BOTTOM){
final ArrayList<TaskStack> stacks = displayContent.getStacks();
for (int i = stacks.size() - 1; i >= 0; --i) {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 1dbca96..c7c9cbf 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -572,7 +572,8 @@
WindowSurfaceController createSurfaceLocked() {
final WindowState w = mWin;
if (w.hasSavedSurface()) {
- Slog.i(TAG, "***** createSurface: " + this + ": called when we had a saved surface");
+ if (DEBUG_ANIM) Slog.i(TAG,
+ "createSurface: " + this + ": called when we had a saved surface");
w.restoreSavedSurface();
return mSurfaceController;
}
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index e14cfdf..3219bfe 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -130,7 +130,7 @@
void destroyInTransaction() {
// if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) {
- Slog.i(TAG, "Destroying surface " + this + " called by " + Debug.getCallers(3));
+ Slog.i(TAG, "Destroying surface " + this + " called by " + Debug.getCallers(4));
// }
try {
mSurfaceControl.destroy();
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index d1d4721..4e1b644 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -647,14 +647,6 @@
for (int i = windows.size() - 1; i >= 0; i--) {
WindowState w = windows.get(i);
- Task task = w.getTask();
- if (task == null && w.getDisplayContent().getHomeStack() == null
- && w.getAttrs().type != TYPE_PRIVATE_PRESENTATION) {
- // TODO: Understand what the use case is here and see if the conditions can be
- // simplified.
- continue;
- }
-
final boolean obscuredChanged = w.mObscured != mObscured;
// Update effect.
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
index d1b8648..bb571c3 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -28,6 +28,7 @@
$(LOCAL_REL_DIR)/com_android_server_SerialService.cpp \
$(LOCAL_REL_DIR)/com_android_server_SystemServer.cpp \
$(LOCAL_REL_DIR)/com_android_server_tv_TvInputHal.cpp \
+ $(LOCAL_REL_DIR)/com_android_server_vr_VrManagerService.cpp \
$(LOCAL_REL_DIR)/com_android_server_UsbDeviceManager.cpp \
$(LOCAL_REL_DIR)/com_android_server_UsbMidiDevice.cpp \
$(LOCAL_REL_DIR)/com_android_server_UsbHostManager.cpp \
diff --git a/services/core/jni/com_android_server_vr_VrManagerService.cpp b/services/core/jni/com_android_server_vr_VrManagerService.cpp
new file mode 100644
index 0000000..1aba43b2
--- /dev/null
+++ b/services/core/jni/com_android_server_vr_VrManagerService.cpp
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "VrManagerService"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <jni.h>
+#include <JNIHelp.h>
+
+#include <utils/Errors.h>
+#include <utils/Log.h>
+#include <hardware/hardware.h>
+#include <hardware/vr.h>
+
+namespace android {
+
+static vr_module_t *gVrHardwareModule = NULL;
+
+
+static void init_native(JNIEnv* /* env */, jclass /* clazz */) {
+ if (gVrHardwareModule != NULL) {
+ // This call path should never be hit.
+ ALOGE("%s: May not initialize VR hardware module more than once!", __FUNCTION__);
+ return;
+ }
+
+ int err = hw_get_module(VR_HARDWARE_MODULE_ID, (hw_module_t const**)&gVrHardwareModule);
+ if (err) {
+ ALOGW("%s: Could not open VR hardware module, error %s (%d).", __FUNCTION__,
+ strerror(-err), err);
+ return;
+ }
+
+ // Call init method if implemented.
+ if (gVrHardwareModule->init) {
+ gVrHardwareModule->init(gVrHardwareModule);
+ }
+}
+
+static void setVrMode_native(JNIEnv* /* env */, jclass /* clazz */, jboolean enabled) {
+ if (gVrHardwareModule == NULL) {
+ // There is no VR hardware module implemented, do nothing.
+ return;
+ }
+
+ // Call set_vr_mode method, this must be implemented if the HAL exists.
+ gVrHardwareModule->set_vr_mode(gVrHardwareModule, static_cast<bool>(enabled));
+}
+
+static const JNINativeMethod method_table[] = {
+ { "initializeNative", "()V", (void*)init_native },
+ { "setVrModeNative", "(Z)V", (void*)setVrMode_native },
+};
+
+int register_android_server_vr_VrManagerService(JNIEnv *env)
+{
+ return jniRegisterNativeMethods(env, "com/android/server/vr/VrManagerService",
+ method_table, NELEM(method_table));
+}
+
+}; // namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index a7010bc..ef5c56c 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -35,6 +35,7 @@
int register_android_server_UsbDeviceManager(JNIEnv* env);
int register_android_server_UsbMidiDevice(JNIEnv* env);
int register_android_server_UsbHostManager(JNIEnv* env);
+int register_android_server_vr_VrManagerService(JNIEnv* env);
int register_android_server_VibratorService(JNIEnv* env);
int register_android_server_location_GnssLocationProvider(JNIEnv* env);
int register_android_server_location_FlpHardwareProvider(JNIEnv* env);
@@ -69,6 +70,7 @@
register_android_server_UsbDeviceManager(env);
register_android_server_UsbMidiDevice(env);
register_android_server_UsbHostManager(env);
+ register_android_server_vr_VrManagerService(env);
register_android_server_VibratorService(env);
register_android_server_SystemServer(env);
register_android_server_location_GnssLocationProvider(env);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 908d2f0..f296d68 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -4132,7 +4132,7 @@
public void choosePrivateKeyAlias(final int uid, final Uri uri, final String alias,
final IBinder response) {
// Caller UID needs to be trusted, so we restrict this method to SYSTEM_UID callers.
- if (!UserHandle.isSameApp(mInjector.binderGetCallingUid(), Process.SYSTEM_UID)) {
+ if (!isCallerWithSystemUid()) {
return;
}
@@ -5860,8 +5860,7 @@
}
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS, null);
- if (hasUserSetupCompleted(userHandle)
- && !UserHandle.isSameApp(callingUid, Process.SYSTEM_UID)) {
+ if (hasUserSetupCompleted(userHandle) && !isCallerWithSystemUid()) {
throw new IllegalStateException("Cannot set the profile owner on a user which is "
+ "already set-up");
}
@@ -5921,8 +5920,7 @@
private void enforceManageUsers() {
final int callingUid = mInjector.binderGetCallingUid();
- if (!(UserHandle.isSameApp(callingUid, Process.SYSTEM_UID)
- || callingUid == Process.ROOT_UID)) {
+ if (!(isCallerWithSystemUid() || callingUid == Process.ROOT_UID)) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null);
}
}
@@ -5945,8 +5943,7 @@
if (userHandle == UserHandle.getUserId(callingUid)) {
return;
}
- if (!(UserHandle.isSameApp(callingUid, Process.SYSTEM_UID)
- || callingUid == Process.ROOT_UID)) {
+ if (!(isCallerWithSystemUid() || callingUid == Process.ROOT_UID)) {
mContext.enforceCallingOrSelfPermission(permission,
"Must be system or have " + permission + " permission");
}
@@ -5964,6 +5961,10 @@
}
}
+ private boolean isCallerWithSystemUid() {
+ return UserHandle.isSameApp(mInjector.binderGetCallingUid(), Process.SYSTEM_UID);
+ }
+
private int getProfileParentId(int userHandle) {
final long ident = mInjector.binderClearCallingIdentity();
try {
@@ -6248,7 +6249,7 @@
@Override
public ComponentName getRestrictionsProvider(int userHandle) {
synchronized (this) {
- if (!UserHandle.isSameApp(mInjector.binderGetCallingUid(), Process.SYSTEM_UID)) {
+ if (!isCallerWithSystemUid()) {
throw new SecurityException("Only the system can query the permission provider");
}
DevicePolicyData userData = getUserData(userHandle);
@@ -6321,8 +6322,7 @@
* permittedList or are a system app.
*/
private boolean checkPackagesInPermittedListOrSystem(List<String> enabledPackages,
- List<String> permittedList) {
- int userIdToCheck = UserHandle.getCallingUserId();
+ List<String> permittedList, int userIdToCheck) {
long id = mInjector.binderClearCallingIdentity();
try {
// If we have an enabled packages list for a managed profile the packages
@@ -6389,7 +6389,8 @@
for (AccessibilityServiceInfo service : enabledServices) {
enabledPackages.add(service.getResolveInfo().serviceInfo.packageName);
}
- if (!checkPackagesInPermittedListOrSystem(enabledPackages, packageList)) {
+ if (!checkPackagesInPermittedListOrSystem(enabledPackages, packageList,
+ userId)) {
Slog.e(LOG_TAG, "Cannot set permitted accessibility services, "
+ "because it contains already enabled accesibility services.");
return false;
@@ -6481,6 +6482,28 @@
}
}
+ @Override
+ public boolean isAccessibilityServicePermittedByAdmin(ComponentName who, String packageName,
+ int userHandle) {
+ if (!mHasFeature) {
+ return true;
+ }
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ Preconditions.checkStringNotEmpty(packageName, "packageName is null");
+ if (!isCallerWithSystemUid()){
+ throw new SecurityException(
+ "Only the system can query if an accessibility service is disabled by admin");
+ }
+ synchronized (this) {
+ ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle);
+ if (admin.permittedAccessiblityServices == null) {
+ return true;
+ }
+ return checkPackagesInPermittedListOrSystem(Arrays.asList(packageName),
+ admin.permittedAccessiblityServices, userHandle);
+ }
+ }
+
private boolean checkCallerIsCurrentUserOrProfile() {
int callingUserId = UserHandle.getCallingUserId();
long token = mInjector.binderClearCallingIdentity();
@@ -6536,7 +6559,8 @@
for (InputMethodInfo ime : enabledImes) {
enabledPackages.add(ime.getPackageName());
}
- if (!checkPackagesInPermittedListOrSystem(enabledPackages, packageList)) {
+ if (!checkPackagesInPermittedListOrSystem(enabledPackages, packageList,
+ mInjector.binderGetCallingUserHandle().getIdentifier())) {
Slog.e(LOG_TAG, "Cannot set permitted input methods, "
+ "because it contains already enabled input method.");
return false;
@@ -6629,6 +6653,28 @@
}
@Override
+ public boolean isInputMethodPermittedByAdmin(ComponentName who, String packageName,
+ int userHandle) {
+ if (!mHasFeature) {
+ return true;
+ }
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ Preconditions.checkStringNotEmpty(packageName, "packageName is null");
+ if (!isCallerWithSystemUid()) {
+ throw new SecurityException(
+ "Only the system can query if an input method is disabled by admin");
+ }
+ synchronized (this) {
+ ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle);
+ if (admin.permittedInputMethods == null) {
+ return true;
+ }
+ return checkPackagesInPermittedListOrSystem(Arrays.asList(packageName),
+ admin.permittedInputMethods, userHandle);
+ }
+ }
+
+ @Override
public UserHandle createUser(ComponentName who, String name) {
Preconditions.checkNotNull(who, "ComponentName is null");
synchronized (this) {
@@ -7425,7 +7471,7 @@
@Override
public void notifyLockTaskModeChanged(boolean isEnabled, String pkg, int userHandle) {
- if (!UserHandle.isSameApp(mInjector.binderGetCallingUid(), Process.SYSTEM_UID)) {
+ if (!isCallerWithSystemUid()) {
throw new SecurityException("notifyLockTaskModeChanged can only be called by system");
}
synchronized (this) {
@@ -8180,7 +8226,7 @@
return null;
}
Preconditions.checkNotNull(who, "ComponentName is null");
- if (!UserHandle.isSameApp(mInjector.binderGetCallingUid(), Process.SYSTEM_UID)) {
+ if (!isCallerWithSystemUid()) {
throw new SecurityException("Only the system can query support message for user");
}
synchronized (this) {
@@ -8198,7 +8244,7 @@
return null;
}
Preconditions.checkNotNull(who, "ComponentName is null");
- if (!UserHandle.isSameApp(mInjector.binderGetCallingUid(), Process.SYSTEM_UID)) {
+ if (!isCallerWithSystemUid()) {
throw new SecurityException("Only the system can query support message for user");
}
synchronized (this) {
diff --git a/services/usb/java/com/android/server/usb/MtpNotificationManager.java b/services/usb/java/com/android/server/usb/MtpNotificationManager.java
index 203d35e..17039bb 100644
--- a/services/usb/java/com/android/server/usb/MtpNotificationManager.java
+++ b/services/usb/java/com/android/server/usb/MtpNotificationManager.java
@@ -30,7 +30,6 @@
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.os.UserHandle;
-import android.util.Log;
/**
* Manager for MTP storage notification.
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 72ff272..c122c5a 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1414,6 +1414,23 @@
return result;
}
+ /**
+ * Launches the {@link android.app.Activity} to manage blocked numbers.
+ * <p> This method displays the UI to manage blocked numbers only if
+ * {@link android.provider.BlockedNumberContract#canCurrentUserBlockNumbers(Context)} returns
+ * {@code true} for the current user.
+ */
+ public void launchManageBlockedNumbersActivity() {
+ ITelecomService service = getTelecomService();
+ if (service != null) {
+ try {
+ service.launchManageBlockedNumbersActivity(mContext.getPackageName());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelecomService#manageBlockedNumbers", e);
+ }
+ }
+ }
+
private ITelecomService getTelecomService() {
if (mTelecomServiceOverride != null) {
return mTelecomServiceOverride;
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index d1d6e0d..6229ed9 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -25,6 +25,7 @@
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.Typeface;
+import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.DisplayMetrics;
@@ -338,7 +339,7 @@
public static String givePrintableIccid(String iccId) {
String iccIdToPrint = null;
if (iccId != null) {
- if (iccId.length() > 9) {
+ if (iccId.length() > 9 && !Build.IS_DEBUGGABLE) {
iccIdToPrint = iccId.substring(0, 9) + "XXXXXXXXXXX";
} else {
iccIdToPrint = iccId;
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index b028ce6..de7b9c2 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -356,6 +356,15 @@
</activity>
<activity
+ android:name="MovingSurfaceViewActivity"
+ android:label="SurfaceView/Animated Movement">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="com.android.test.hwui.TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity
android:name="GLTextureViewActivity"
android:label="TextureView/OpenGL">
<intent-filter>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/MovingSurfaceViewActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/MovingSurfaceViewActivity.java
new file mode 100644
index 0000000..cd15ef1
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/MovingSurfaceViewActivity.java
@@ -0,0 +1,125 @@
+/*
+ * 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.test.hwui;
+
+import android.animation.ObjectAnimator;
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.SurfaceHolder;
+import android.view.SurfaceHolder.Callback;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.animation.LinearInterpolator;
+import android.widget.FrameLayout;
+
+public class MovingSurfaceViewActivity extends Activity implements Callback {
+ static final String TAG = "MovingSurfaceView";
+ SurfaceView mSurfaceView;
+ ObjectAnimator mAnimator;
+
+ class MySurfaceView extends SurfaceView {
+ boolean mSlowToggled;
+
+ public MySurfaceView(Context context) {
+ super(context);
+ setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mSlowToggled = !mSlowToggled;
+ Log.d(TAG, "SLOW MODE: " + mSlowToggled);
+ invalidate();
+ }
+ });
+ setWillNotDraw(false);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+ if (mSlowToggled) {
+ try {
+ Thread.sleep(16);
+ } catch (InterruptedException e) {}
+ }
+ }
+
+ public void setMyTranslationY(float ty) {
+ setTranslationY(ty);
+ if (mSlowToggled) {
+ invalidate();
+ }
+ }
+
+ public float getMyTranslationY() {
+ return getTranslationY();
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ FrameLayout content = new FrameLayout(this);
+
+ mSurfaceView = new MySurfaceView(this);
+ mSurfaceView.getHolder().addCallback(this);
+
+ final float density = getResources().getDisplayMetrics().density;
+ int size = (int) (200 * density);
+
+ content.addView(mSurfaceView, new FrameLayout.LayoutParams(
+ size, size, Gravity.CENTER));
+ mAnimator = ObjectAnimator.ofFloat(mSurfaceView, "myTranslationY",
+ 0, size);
+ mAnimator.setRepeatMode(ObjectAnimator.REVERSE);
+ mAnimator.setRepeatCount(ObjectAnimator.INFINITE);
+ mAnimator.setDuration(200);
+ mAnimator.setInterpolator(new LinearInterpolator());
+ setContentView(content);
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ Canvas canvas = holder.lockCanvas();
+ canvas.drawARGB(0xFF, 0x00, 0xFF, 0x00);
+ holder.unlockCanvasAndPost(canvas);
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mAnimator.start();
+ }
+
+ @Override
+ protected void onPause() {
+ mAnimator.pause();
+ super.onPause();
+ }
+}
diff --git a/wifi/java/android/net/wifi/AnqpInformationElement.java b/wifi/java/android/net/wifi/AnqpInformationElement.java
new file mode 100644
index 0000000..47b7129
--- /dev/null
+++ b/wifi/java/android/net/wifi/AnqpInformationElement.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 android.net.wifi;
+
+/**
+ * This object contains the payload of an ANQP element.
+ * Vendor id is the vendor ID for the element, or 0 if it is an 802.11(u) element.
+ * Hotspot 2.0 uses the WFA Vendor ID which is 0x506f9a
+ * The payload contains the bytes of the payload, starting after the length octet(s).
+ * @hide
+ */
+public class AnqpInformationElement {
+ public static final int HOTSPOT20_VENDOR_ID = 0x506f9a;
+
+ public static final int ANQP_QUERY_LIST = 256;
+ public static final int ANQP_CAPABILITY_LIST = 257;
+ public static final int ANQP_VENUE_NAME = 258;
+ public static final int ANQP_EMERGENCY_NUMBER = 259;
+ public static final int ANQP_NWK_AUTH_TYPE = 260;
+ public static final int ANQP_ROAMING_CONSORTIUM = 261;
+ public static final int ANQP_IP_ADDR_AVAILABILITY = 262;
+ public static final int ANQP_NAI_REALM = 263;
+ public static final int ANQP_3GPP_NETWORK = 264;
+ public static final int ANQP_GEO_LOC = 265;
+ public static final int ANQP_CIVIC_LOC = 266;
+ public static final int ANQP_LOC_URI = 267;
+ public static final int ANQP_DOM_NAME = 268;
+ public static final int ANQP_EMERGENCY_ALERT = 269;
+ public static final int ANQP_TDLS_CAP = 270;
+ public static final int ANQP_EMERGENCY_NAI = 271;
+ public static final int ANQP_NEIGHBOR_REPORT = 272;
+ public static final int ANQP_VENDOR_SPEC = 56797;
+
+ public static final int HS_QUERY_LIST = 1;
+ public static final int HS_CAPABILITY_LIST = 2;
+ public static final int HS_FRIENDLY_NAME = 3;
+ public static final int HS_WAN_METRICS = 4;
+ public static final int HS_CONN_CAPABILITY = 5;
+ public static final int HS_NAI_HOME_REALM_QUERY = 6;
+ public static final int HS_OPERATING_CLASS = 7;
+ public static final int HS_OSU_PROVIDERS = 8;
+ public static final int HS_ICON_REQUEST = 10;
+ public static final int HS_ICON_FILE = 11;
+
+ private final int mVendorId;
+ private final int mElementId;
+ private final byte[] mPayload;
+
+ public AnqpInformationElement(int vendorId, int elementId, byte[] payload) {
+ mVendorId = vendorId;
+ mElementId = elementId;
+ mPayload = payload;
+ }
+
+ public int getVendorId() {
+ return mVendorId;
+ }
+
+ public int getElementId() {
+ return mElementId;
+ }
+
+ public byte[] getPayload() {
+ return mPayload;
+ }
+}
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index bad4e9c..1c7a308 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -20,7 +20,7 @@
import android.net.wifi.WifiInfo;
import android.net.wifi.ScanSettings;
import android.net.wifi.ScanResult;
-import android.net.wifi.ScanInfo;
+import android.net.wifi.PasspointManagementObjectDefinition;
import android.net.wifi.WifiConnectionStatistics;
import android.net.wifi.WifiActivityEnergyInfo;
import android.net.Network;
@@ -50,6 +50,17 @@
int addOrUpdateNetwork(in WifiConfiguration config);
+ int addPasspointManagementObject(String mo);
+
+ int modifyPasspointManagementObject(String fqdn,
+ in List<PasspointManagementObjectDefinition> mos);
+
+ void queryPasspointIcon(long bssid, String fileName);
+
+ int matchProviderWithCurrentNetwork(String fqdn);
+
+ void deauthenticateNetwork(long holdoff, boolean ess);
+
boolean removeNetwork(int netId);
boolean enableNetwork(int netId, boolean disableOthers);
@@ -64,10 +75,6 @@
void disconnect();
- List<ScanInfo> getScanInfos(String callingPackage);
-
- void setOsuSelection(int osuID);
-
void reconnect();
void reassociate();
diff --git a/wifi/java/android/net/wifi/ScanInfo.aidl b/wifi/java/android/net/wifi/PasspointManagementObjectDefinition.aidl
similarity index 84%
rename from wifi/java/android/net/wifi/ScanInfo.aidl
rename to wifi/java/android/net/wifi/PasspointManagementObjectDefinition.aidl
index 18ae508..eb7cc39 100644
--- a/wifi/java/android/net/wifi/ScanInfo.aidl
+++ b/wifi/java/android/net/wifi/PasspointManagementObjectDefinition.aidl
@@ -1,5 +1,5 @@
/**
- * Copyright (c) 2015, The Android Open Source Project
+ * Copyright (c) 2008, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,4 +16,4 @@
package android.net.wifi;
-parcelable ScanInfo;
+parcelable PasspointManagementObjectDefinition;
diff --git a/wifi/java/android/net/wifi/PasspointManagementObjectDefinition.java b/wifi/java/android/net/wifi/PasspointManagementObjectDefinition.java
new file mode 100644
index 0000000..611ed15
--- /dev/null
+++ b/wifi/java/android/net/wifi/PasspointManagementObjectDefinition.java
@@ -0,0 +1,81 @@
+/*
+ * 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.net.wifi;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This object describes a partial tree structure in the Hotspot 2.0 release 2 management object.
+ * The object is used during subscription remediation to modify parts of an existing PPS MO
+ * tree (Hotspot 2.0 specification section 9.1).
+ * @hide
+ */
+public class PasspointManagementObjectDefinition implements Parcelable {
+ private final String mBaseUri;
+ private final String mUrn;
+ private final String mMoTree;
+
+ public PasspointManagementObjectDefinition(String baseUri, String urn, String moTree) {
+ mBaseUri = baseUri;
+ mUrn = urn;
+ mMoTree = moTree;
+ }
+
+ public String getmBaseUri() {
+ return mBaseUri;
+ }
+
+ public String getmUrn() {
+ return mUrn;
+ }
+
+ public String getmMoTree() {
+ return mMoTree;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mBaseUri);
+ dest.writeString(mUrn);
+ dest.writeString(mMoTree);
+ }
+
+ /**
+ * Implement the Parcelable interface {@hide}
+ */
+ public static final Creator<PasspointManagementObjectDefinition> CREATOR =
+ new Creator<PasspointManagementObjectDefinition>() {
+ public PasspointManagementObjectDefinition createFromParcel(Parcel in) {
+ return new PasspointManagementObjectDefinition(
+ in.readString(), /* base URI */
+ in.readString(), /* URN */
+ in.readString() /* Tree XML */
+ );
+ }
+
+ public PasspointManagementObjectDefinition[] newArray(int size) {
+ return new PasspointManagementObjectDefinition[size];
+ }
+ };
+}
+
diff --git a/wifi/java/android/net/wifi/ScanInfo.java b/wifi/java/android/net/wifi/ScanInfo.java
deleted file mode 100644
index 39186fa..0000000
--- a/wifi/java/android/net/wifi/ScanInfo.java
+++ /dev/null
@@ -1,189 +0,0 @@
-package android.net.wifi;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-public class ScanInfo implements Parcelable {
- private final ScanResult mScanResult;
-
- private final long mBSSID; // The BSSID of the best AP with an SSID matching the OSU
- private final int mRSSI; // RSSI of the AP with BSSID
- private final String mSSID; // The SSID to connect to for an OSU connection.
- private final String mName;
- private final String mServiceDescription;
- private final String mIconType;
- private final byte[] mIconData;
- private final int mOSUIdentity;
-
- public ScanInfo(ScanResult scanResult) {
- mScanResult = scanResult;
-
- mBSSID = -1;
- mRSSI = -1;
- mSSID = null;
- mName = null;
- mServiceDescription = null;
- mIconType = null;
- mIconData = null;
- mOSUIdentity = -1;
- }
-
- public ScanInfo(long BSSID, int rssi, String SSID, String name, String serviceDescription,
- String iconType, byte[] iconData, int OSUIdentity) {
- mBSSID = BSSID;
- mRSSI = rssi;
- mSSID = SSID;
- mName = name;
- mServiceDescription = serviceDescription;
- mIconType = iconType;
- mIconData = iconData;
- mOSUIdentity = OSUIdentity;
-
- mScanResult = null;
- }
-
- /**
- * Get the scan result of this ScanInfo.
- * @return The ScanResult, if this ScanInfo contains a one. If the ScanInfo contains
- * OSU information getScanResult will return null.
- */
- public ScanResult getScanResult() {
- return mScanResult;
- }
-
- /**
- * OSU only: The BSSID of the AP who advertises the OSU SSID. This value is not guaranteed to
- * be correct; In the somewhat unlikely case that multiple APs advertise OSU SSIDs that matches
- * an OSU information element returned through ANQP and one of those is not related to an OSU
- * there is a (slight) risk that the BSSID is for a "spoof" OSU.
- * The matching algorithm that produces the ScanInfo objects makes a best effort to get the
- * matching right though and since it is (a) fair to assume that the OSU SSID resides on the
- * same AP as the one advertising the OSU information, and (b) BSSIDs for multi-SSID APs are
- * typically adjacent to each other, matching will prefer the BSSID closest to the advertising
- * APs BSSID if multiple SSIDs match.
- * @return The BSSID.
- */
- public long getBssid() {
- return mBSSID;
- }
-
- /**
- * OSU only.
- * @return The signal level of the AP associated with the BSSID from getBSSID.
- */
- public int getRssi() {
- return mRSSI;
- }
-
- /**
- * OSU only.
- * @return The SSID of the AP to which to associate to establish an OSU connection.
- */
- public String getSsid() {
- return mSSID;
- }
-
- /**
- * OSU only.
- * @return The name of the Service Provider of the OSU.
- */
- public String getName() {
- return mName;
- }
-
- /**
- * OSU only.
- * @return The service description of the OSU.
- */
- public String getServiceDescription() {
- return mServiceDescription;
- }
-
- /**
- * OSU only.
- * Get the type of icon that icon data represents, e.g. JPG, PNG etc. This field is formatted
- * using standard MIME encodings per RFC-4288 and IANA MIME media types.
- * @return The icon type in icon data.
- */
- public String getIconType() {
- return mIconType;
- }
-
- /**
- * OSU only.
- * @return The binary data of the icon.
- */
- public byte[] getIconData() {
- return mIconData;
- }
-
- /**
- * OSU only.
- * @return a unique identity for the OSU. This value is generated by the framework and should
- * be used to uniquely identify a specific OSU. Please note that values may be reused after
- * a very long time-span (in any normal scenario, likely years) and implementations should make
- * sure to not rely on any long term persisted values.
- */
- public int getOsuIdentity() {
- return mOSUIdentity;
- }
-
- private static final int ScanResultMarker = 0;
- private static final int OSUMarker = 1;
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- /** Implement the Parcelable interface {@hide} */
- public static final Creator<ScanInfo> CREATOR =
- new Creator<ScanInfo>() {
- @Override
- public ScanInfo createFromParcel(Parcel source) {
- int marker = source.readInt();
- if (marker == ScanResultMarker) {
- return new ScanInfo(ScanResult.CREATOR.createFromParcel(source));
- }
- else if (marker == OSUMarker) {
- return new ScanInfo(
- source.readLong(),
- source.readInt(),
- source.readString(),
- source.readString(),
- source.readString(),
- source.readString(),
- source.createByteArray(),
- source.readInt()
- );
- }
- else {
- throw new RuntimeException("Bad ScanInfo data");
- }
- }
-
- @Override
- public ScanInfo[] newArray(int size) {
- return new ScanInfo[0];
- }
- };
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- if (mScanResult != null) {
- dest.writeInt(ScanResultMarker);
- mScanResult.writeToParcel(dest, flags);
- return;
- }
-
- dest.writeInt(OSUMarker);
- dest.writeLong(mBSSID);
- dest.writeInt(mRSSI);
- dest.writeString(mSSID);
- dest.writeString(mName);
- dest.writeString(mServiceDescription);
- dest.writeString(mIconType);
- dest.writeByteArray(mIconData);
- dest.writeInt(mOSUIdentity);
- }
-}
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index 1f05339..4a86c59 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -43,6 +43,19 @@
* The address of the access point.
*/
public String BSSID;
+
+ /**
+ * The HESSID from the beacon.
+ * @hide
+ */
+ public long hessid;
+
+ /**
+ * The ANQP Domain ID from the Hotspot 2.0 Indication element, if present.
+ * @hide
+ */
+ public int anqpDomainId;
+
/**
* Describes the authentication, key management, and encryption schemes
* supported by the access point.
@@ -341,14 +354,27 @@
/** information elements found in the beacon
* @hide
*/
- public InformationElement informationElements[];
+ public InformationElement[] informationElements;
+
+ /** ANQP response elements.
+ * @hide
+ */
+ public AnqpInformationElement[] anqpElements;
/** {@hide} */
- public ScanResult(WifiSsid wifiSsid, String BSSID, String caps, int level, int frequency,
- long tsf) {
+ public ScanResult(WifiSsid wifiSsid, String BSSID, long hessid, int anqpDomainId,
+ byte[] osuProviders, String caps, int level, int frequency, long tsf) {
this.wifiSsid = wifiSsid;
this.SSID = (wifiSsid != null) ? wifiSsid.toString() : WifiSsid.NONE;
this.BSSID = BSSID;
+ this.hessid = hessid;
+ this.anqpDomainId = anqpDomainId;
+ if (osuProviders != null) {
+ this.anqpElements = new AnqpInformationElement[1];
+ this.anqpElements[0] =
+ new AnqpInformationElement(AnqpInformationElement.HOTSPOT20_VENDOR_ID,
+ AnqpInformationElement.HS_OSU_PROVIDERS, osuProviders);
+ }
this.capabilities = caps;
this.level = level;
this.frequency = frequency;
@@ -380,11 +406,14 @@
}
/** {@hide} */
- public ScanResult(String Ssid, String BSSID, String caps, int level, int frequency,
+ public ScanResult(String Ssid, String BSSID, long hessid, int anqpDomainId, String caps,
+ int level, int frequency,
long tsf, int distCm, int distSdCm, int channelWidth, int centerFreq0, int centerFreq1,
boolean is80211McRTTResponder) {
this.SSID = Ssid;
this.BSSID = BSSID;
+ this.hessid = hessid;
+ this.anqpDomainId = anqpDomainId;
this.capabilities = caps;
this.level = level;
this.frequency = frequency;
@@ -402,11 +431,12 @@
}
/** {@hide} */
- public ScanResult(WifiSsid wifiSsid, String Ssid, String BSSID, String caps, int level,
+ public ScanResult(WifiSsid wifiSsid, String Ssid, String BSSID, long hessid, int anqpDomainId,
+ String caps, int level,
int frequency, long tsf, int distCm, int distSdCm, int channelWidth,
int centerFreq0, int centerFreq1, boolean is80211McRTTResponder) {
- this(Ssid, BSSID, caps,level, frequency, tsf, distCm, distSdCm, channelWidth, centerFreq0,
- centerFreq1, is80211McRTTResponder);
+ this(Ssid, BSSID, hessid, anqpDomainId, caps, level, frequency, tsf, distCm,
+ distSdCm, channelWidth, centerFreq0, centerFreq1, is80211McRTTResponder);
this.wifiSsid = wifiSsid;
}
@@ -416,6 +446,10 @@
wifiSsid = source.wifiSsid;
SSID = source.SSID;
BSSID = source.BSSID;
+ hessid = source.hessid;
+ anqpDomainId = source.anqpDomainId;
+ informationElements = source.informationElements;
+ anqpElements = source.anqpElements;
capabilities = source.capabilities;
level = source.level;
frequency = source.frequency;
@@ -496,6 +530,8 @@
}
dest.writeString(SSID);
dest.writeString(BSSID);
+ dest.writeLong(hessid);
+ dest.writeInt(anqpDomainId);
dest.writeString(capabilities);
dest.writeInt(level);
dest.writeInt(frequency);
@@ -532,6 +568,18 @@
for (int i = 0; i < anqpLines.size(); i++) {
dest.writeString(anqpLines.get(i));
}
+ }
+ else {
+ dest.writeInt(0);
+ }
+ if (anqpElements != null) {
+ dest.writeInt(anqpElements.length);
+ for (AnqpInformationElement element : anqpElements) {
+ dest.writeInt(element.getVendorId());
+ dest.writeInt(element.getElementId());
+ dest.writeInt(element.getPayload().length);
+ dest.writeByteArray(element.getPayload());
+ }
} else {
dest.writeInt(0);
}
@@ -546,19 +594,22 @@
wifiSsid = WifiSsid.CREATOR.createFromParcel(in);
}
ScanResult sr = new ScanResult(
- wifiSsid,
- in.readString(), /* SSID */
- in.readString(), /* BSSID */
- in.readString(), /* capabilities */
- in.readInt(), /* level */
- in.readInt(), /* frequency */
- in.readLong(), /* timestamp */
- in.readInt(), /* distanceCm */
- in.readInt(), /* distanceSdCm */
- in.readInt(), /* channelWidth */
- in.readInt(), /* centerFreq0 */
- in.readInt(), /* centerFreq1 */
- false /* rtt responder, fixed with flags below */
+ wifiSsid,
+ in.readString(), /* SSID */
+ in.readString(), /* BSSID */
+ in.readLong(), /* HESSID */
+ in.readInt(), /* ANQP Domain ID */
+ in.readString(), /* capabilities */
+ in.readInt(), /* level */
+ in.readInt(), /* frequency */
+ in.readLong(), /* timestamp */
+ in.readInt(), /* distanceCm */
+ in.readInt(), /* distanceSdCm */
+ in.readInt(), /* channelWidth */
+ in.readInt(), /* centerFreq0 */
+ in.readInt(), /* centerFreq1 */
+ false /* rtt responder,
+ fixed with flags below */
);
sr.seen = in.readLong();
@@ -590,6 +641,19 @@
sr.anqpLines.add(in.readString());
}
}
+ n = in.readInt();
+ if (n != 0) {
+ sr.anqpElements = new AnqpInformationElement[n];
+ for (int i = 0; i < n; i++) {
+ int vendorId = in.readInt();
+ int elementId = in.readInt();
+ int len = in.readInt();
+ byte[] payload = new byte[len];
+ in.readByteArray(payload);
+ sr.anqpElements[i] =
+ new AnqpInformationElement(vendorId, elementId, payload);
+ }
+ }
return sr;
}
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 4c22460..a9132a5 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -58,6 +58,9 @@
/** {@hide} */
public static final int INVALID_NETWORK_ID = -1;
+ /** {@hide} */
+ private String mPasspointManagementObjectTree;
+
/**
* Recognized key management schemes.
*/
@@ -80,11 +83,16 @@
*/
@SystemApi
public static final int WPA2_PSK = 4;
+ /**
+ * Hotspot 2.0 r2 OSEN:
+ * @hide
+ */
+ public static final int OSEN = 5;
public static final String varName = "key_mgmt";
public static final String[] strings = { "NONE", "WPA_PSK", "WPA_EAP", "IEEE8021X",
- "WPA2_PSK" };
+ "WPA2_PSK", "OSEN" };
}
/**
@@ -97,10 +105,14 @@
public static final int WPA = 0;
/** WPA2/IEEE 802.11i */
public static final int RSN = 1;
+ /** HS2.0 r2 OSEN
+ * @hide
+ */
+ public static final int OSEN = 2;
public static final String varName = "proto";
- public static final String[] strings = { "WPA", "RSN" };
+ public static final String[] strings = { "WPA", "RSN", "OSEN" };
}
/**
@@ -159,10 +171,15 @@
public static final int TKIP = 2;
/** AES in Counter mode with CBC-MAC [RFC 3610, IEEE 802.11i/D7.0] */
public static final int CCMP = 3;
+ /** Hotspot 2.0 r2 OSEN
+ * @hide
+ */
+ public static final int GTK_NOT_USED = 4;
public static final String varName = "group";
- public static final String[] strings = { "WEP40", "WEP104", "TKIP", "CCMP" };
+ public static final String[] strings =
+ { "WEP40", "WEP104", "TKIP", "CCMP", "GTK_NOT_USED" };
}
/** Possible status of a network configuration. */
@@ -1735,6 +1752,16 @@
return shared || (UserHandle.getUserId(creatorUid) == userId);
}
+ /** @hide */
+ public void setPasspointManagementObjectTree(String passpointManagementObjectTree) {
+ mPasspointManagementObjectTree = passpointManagementObjectTree;
+ }
+
+ /** @hide */
+ public String getMoTree() {
+ return mPasspointManagementObjectTree;
+ }
+
/** copy constructor {@hide} */
public WifiConfiguration(WifiConfiguration source) {
if (source != null) {
@@ -1886,6 +1913,7 @@
dest.writeInt(numNoInternetAccessReports);
dest.writeInt(noInternetAccessExpected ? 1 : 0);
dest.writeInt(shared ? 1 : 0);
+ dest.writeString(mPasspointManagementObjectTree);
}
/** Implement the Parcelable interface {@hide} */
@@ -1954,6 +1982,7 @@
config.numNoInternetAccessReports = in.readInt();
config.noInternetAccessExpected = in.readInt() != 0;
config.shared = in.readInt() != 0;
+ config.mPasspointManagementObjectTree = in.readString();
return config;
}
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index 605e8c0..a0dbd85 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -273,8 +273,11 @@
public static final int AKA = 5;
/** EAP-Authentication and Key Agreement Prime */
public static final int AKA_PRIME = 6;
+ /** Hotspot 2.0 r2 OSEN */
+ public static final int UNAUTH_TLS = 7;
/** @hide */
- public static final String[] strings = { "PEAP", "TLS", "TTLS", "PWD", "SIM", "AKA", "AKA'" };
+ public static final String[] strings =
+ { "PEAP", "TLS", "TTLS", "PWD", "SIM", "AKA", "AKA'", "WFA-UNAUTH-TLS" };
/** Prevent initialization */
private Eap() {}
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index db40cd8..2ad3c2e 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -26,28 +26,26 @@
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
-import android.net.wifi.ScanSettings;
import android.os.Binder;
import android.os.Build;
-import android.os.IBinder;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.Messenger;
import android.os.RemoteException;
import android.os.WorkSource;
-import android.os.Messenger;
import android.util.Log;
import android.util.SparseArray;
-import java.net.InetAddress;
-import java.util.concurrent.CountDownLatch;
-
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.AsyncChannel;
import com.android.internal.util.Protocol;
+import java.net.InetAddress;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
/**
* This class provides the primary API for managing all aspects of Wi-Fi
@@ -114,6 +112,51 @@
public static final int WIFI_CREDENTIAL_FORGOT = 1;
/**
+ * Broadcast intent action indicating that the a Passpoint release 2 icon has been received.
+ * @hide
+ */
+ public static final String PASSPOINT_ICON_RECEIVED_ACTION =
+ "android.net.wifi.PASSPOINT_ICON_RECEIVED";
+ /** @hide */
+ public static final String EXTRA_PASSPOINT_ICON_BSSID = "bssid";
+ /** @hide */
+ public static final String EXTRA_PASSPOINT_ICON_FILE = "file";
+ /** @hide */
+ public static final String EXTRA_PASSPOINT_ICON_DATA = "icon";
+
+ /**
+ * Broadcast intent action indicating that the a Passpoint release
+ * 2 WNM frame has been received.
+ * @hide
+ */
+ public static final String PASSPOINT_WNM_FRAME_RECEIVED_ACTION =
+ "android.net.wifi.PASSPOINT_WNM_FRAME_RECEIVED";
+ /**
+ * Originating BSS
+ * @hide */
+ public static final String EXTRA_PASSPOINT_WNM_BSSID = "bssid";
+ /**
+ * SOAP-XML or OMA-DM
+ * @hide */
+ public static final String EXTRA_PASSPOINT_WNM_METHOD = "method";
+ /**
+ * Type of Passpoint match
+ * @hide */
+ public static final String EXTRA_PASSPOINT_WNM_PPOINT_MATCH = "match";
+ /**
+ * String
+ * @hide */
+ public static final String EXTRA_PASSPOINT_WNM_URL = "url";
+ /**
+ * Boolean true=ess, false=bss
+ * @hide */
+ public static final String EXTRA_PASSPOINT_WNM_ESS = "ess";
+ /**
+ * Delay in seconds
+ * @hide */
+ public static final String EXTRA_PASSPOINT_WNM_DELAY = "delay";
+
+ /**
* Broadcast intent action indicating that Wi-Fi has been enabled, disabled,
* enabling, disabling, or unknown. One extra provides this state as an int.
* Another extra provides the previous state, if available.
@@ -779,6 +822,76 @@
}
/**
+ * Add a Hotspot 2.0 release 2 Management Object
+ * @param mo The MO in XML form
+ * @return -1 for failure
+ * @hide
+ */
+ public int addPasspointManagementObject(String mo) {
+ try {
+ return mService.addPasspointManagementObject(mo);
+ } catch (RemoteException e) {
+ return -1;
+ }
+ }
+
+ /**
+ * Modify a Hotspot 2.0 release 2 Management Object
+ * @param fqdn The FQDN of the service provider
+ * @param mos A List of MO definitions to be updated
+ * @return the number of nodes updated, or -1 for failure
+ * @hide
+ */
+ public int modifyPasspointManagementObject(String fqdn,
+ List<PasspointManagementObjectDefinition> mos) {
+ try {
+ return mService.modifyPasspointManagementObject(fqdn, mos);
+ } catch (RemoteException e) {
+ return -1;
+ }
+ }
+
+ /**
+ * Query for a Hotspot 2.0 release 2 OSU icon
+ * @param bssid The BSSID of the AP
+ * @param fileName Icon file name
+ * @hide
+ */
+ public void queryPasspointIcon(long bssid, String fileName) {
+ try {
+ mService.queryPasspointIcon(bssid, fileName);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Match the currently associated network against the SP matching the given FQDN
+ * @param fqdn FQDN of the SP
+ * @return ordinal [HomeProvider, RoamingProvider, Incomplete, None, Declined]
+ * @hide
+ */
+ public int matchProviderWithCurrentNetwork(String fqdn) {
+ try {
+ return mService.matchProviderWithCurrentNetwork(fqdn);
+ } catch (RemoteException e) {
+ return -1;
+ }
+ }
+
+ /**
+ * Deauthenticate and set the re-authentication hold off time for the current network
+ * @param holdoff hold off time in milliseconds
+ * @param ess set if the hold off pertains to an ESS rather than a BSS
+ * @hide
+ */
+ public void deauthenticateNetwork(long holdoff, boolean ess) {
+ try {
+ mService.deauthenticateNetwork(holdoff, ess);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
* Remove the specified network from the list of configured networks.
* This may result in the asynchronous delivery of state change
* events.
@@ -1199,30 +1312,6 @@
}
/**
- * An augmented version of getScanResults that returns ScanResults as well as OSU information
- * wrapped in ScanInfo objects.
- * @return
- */
- public List<ScanInfo> getScanInfos() {
- try {
- return mService.getScanInfos(mContext.getOpPackageName());
- } catch (RemoteException e) {
- return null;
- }
- }
-
- /**
- * Notify the OSU framework about the currently selected OSU.
- * @param osuID The OSU ID from ScanInfo.getOsuIdentity()
- */
- public void setOsuSelection(int osuID) {
- try {
- mService.setOsuSelection(osuID);
- } catch (RemoteException e) {
- }
- }
-
- /**
* Check if scanning is always available.
*
* If this return {@code true}, apps can issue {@link #startScan} and fetch scan results