Merge "Implementation of data usage callbacks." into nyc-dev
diff --git a/api/current.txt b/api/current.txt
index 9e96249..1ebec4e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8006,6 +8006,7 @@
method public final int getColor(int);
method public final android.content.res.ColorStateList getColorStateList(int);
method public abstract android.content.ContentResolver getContentResolver();
+ method public abstract java.io.File getDataDir();
method public abstract java.io.File getDatabasePath(java.lang.String);
method public abstract java.io.File getDir(java.lang.String, int);
method public final android.graphics.drawable.Drawable getDrawable(int);
@@ -8198,6 +8199,7 @@
method public java.lang.ClassLoader getClassLoader();
method public java.io.File getCodeCacheDir();
method public android.content.ContentResolver getContentResolver();
+ method public java.io.File getDataDir();
method public java.io.File getDatabasePath(java.lang.String);
method public java.io.File getDir(java.lang.String, int);
method public java.io.File getExternalCacheDir();
@@ -8534,7 +8536,6 @@
field public static final java.lang.String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL";
field public static final java.lang.String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT";
field public static final java.lang.String ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE";
- field public static final java.lang.String ACTION_OPEN_EXTERNAL_DIRECTORY = "android.intent.action.OPEN_EXTERNAL_DIRECTORY";
field public static final java.lang.String ACTION_PACKAGES_SUSPENDED = "android.intent.action.PACKAGES_SUSPENDED";
field public static final java.lang.String ACTION_PACKAGES_UNSUSPENDED = "android.intent.action.PACKAGES_UNSUSPENDED";
field public static final java.lang.String ACTION_PACKAGE_ADDED = "android.intent.action.PACKAGE_ADDED";
@@ -29365,11 +29366,27 @@
public class StorageManager {
method public java.lang.String getMountedObbPath(java.lang.String);
+ method public android.os.storage.StorageVolume getPrimaryVolume();
+ method public android.os.storage.StorageVolume[] getVolumeList();
method public boolean isObbMounted(java.lang.String);
method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
}
+ public class StorageVolume implements android.os.Parcelable {
+ method public android.content.Intent createAccessIntent(java.lang.String);
+ method public int describeContents();
+ method public java.lang.String getDescription(android.content.Context);
+ method public java.lang.String getState();
+ method public java.lang.String getUuid();
+ method public boolean isEmulated();
+ method public boolean isPrimary();
+ method public boolean isRemovable();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.os.storage.StorageVolume> CREATOR;
+ field public static final java.lang.String EXTRA_STORAGE_VOLUME = "android.os.storage.extra.STORAGE_VOLUME";
+ }
+
}
package android.preference {
@@ -31469,6 +31486,8 @@
}
protected static abstract interface ContactsContract.PhoneLookupColumns {
+ field public static final java.lang.String CONTACT_ID = "contact_id";
+ field public static final java.lang.String DATA_ID = "data_id";
field public static final java.lang.String LABEL = "label";
field public static final java.lang.String NORMALIZED_NUMBER = "normalized_number";
field public static final java.lang.String NUMBER = "number";
@@ -32157,7 +32176,6 @@
field public static final java.lang.String ACTION_SEARCH_SETTINGS = "android.search.action.SEARCH_SETTINGS";
field public static final java.lang.String ACTION_SECURITY_SETTINGS = "android.settings.SECURITY_SETTINGS";
field public static final java.lang.String ACTION_SETTINGS = "android.settings.SETTINGS";
- field public static final java.lang.String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS";
field public static final java.lang.String ACTION_SHOW_REGULATORY_INFO = "android.settings.SHOW_REGULATORY_INFO";
field public static final java.lang.String ACTION_SOUND_SETTINGS = "android.settings.SOUND_SETTINGS";
field public static final java.lang.String ACTION_SYNC_SETTINGS = "android.settings.SYNC_SETTINGS";
@@ -34077,6 +34095,7 @@
method public boolean isDigestsSpecified();
method public boolean isRandomizedEncryptionRequired();
method public boolean isUserAuthenticationRequired();
+ method public boolean isUserAuthenticationValidWhileOnBody();
}
public static final class KeyGenParameterSpec.Builder {
@@ -34099,6 +34118,7 @@
method public android.security.keystore.KeyGenParameterSpec.Builder setRandomizedEncryptionRequired(boolean);
method public android.security.keystore.KeyGenParameterSpec.Builder setSignaturePaddings(java.lang.String...);
method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationRequired(boolean);
+ method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidWhileOnBody(boolean);
method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidityDurationSeconds(int);
}
@@ -34118,6 +34138,7 @@
method public boolean isInsideSecureHardware();
method public boolean isUserAuthenticationRequired();
method public boolean isUserAuthenticationRequirementEnforcedBySecureHardware();
+ method public boolean isUserAuthenticationValidWhileOnBody();
}
public class KeyNotYetValidException extends java.security.InvalidKeyException {
@@ -34180,6 +34201,7 @@
method public boolean isDigestsSpecified();
method public boolean isRandomizedEncryptionRequired();
method public boolean isUserAuthenticationRequired();
+ method public boolean isUserAuthenticationValidWhileOnBody();
}
public static final class KeyProtection.Builder {
@@ -34195,6 +34217,7 @@
method public android.security.keystore.KeyProtection.Builder setRandomizedEncryptionRequired(boolean);
method public android.security.keystore.KeyProtection.Builder setSignaturePaddings(java.lang.String...);
method public android.security.keystore.KeyProtection.Builder setUserAuthenticationRequired(boolean);
+ method public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidWhileOnBody(boolean);
method public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidityDurationSeconds(int);
}
@@ -37611,6 +37634,7 @@
method public java.lang.ClassLoader getClassLoader();
method public java.io.File getCodeCacheDir();
method public android.content.ContentResolver getContentResolver();
+ method public java.io.File getDataDir();
method public java.io.File getDatabasePath(java.lang.String);
method public java.io.File getDir(java.lang.String, int);
method public java.io.File getExternalCacheDir();
diff --git a/api/system-current.txt b/api/system-current.txt
index c7b2604..55df364 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -8297,6 +8297,7 @@
method public final int getColor(int);
method public final android.content.res.ColorStateList getColorStateList(int);
method public abstract android.content.ContentResolver getContentResolver();
+ method public abstract java.io.File getDataDir();
method public abstract java.io.File getDatabasePath(java.lang.String);
method public abstract java.io.File getDir(java.lang.String, int);
method public final android.graphics.drawable.Drawable getDrawable(int);
@@ -8499,6 +8500,7 @@
method public java.lang.ClassLoader getClassLoader();
method public java.io.File getCodeCacheDir();
method public android.content.ContentResolver getContentResolver();
+ method public java.io.File getDataDir();
method public java.io.File getDatabasePath(java.lang.String);
method public java.io.File getDir(java.lang.String, int);
method public java.io.File getExternalCacheDir();
@@ -8840,7 +8842,6 @@
field public static final java.lang.String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL";
field public static final java.lang.String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT";
field public static final java.lang.String ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE";
- field public static final java.lang.String ACTION_OPEN_EXTERNAL_DIRECTORY = "android.intent.action.OPEN_EXTERNAL_DIRECTORY";
field public static final java.lang.String ACTION_PACKAGES_SUSPENDED = "android.intent.action.PACKAGES_SUSPENDED";
field public static final java.lang.String ACTION_PACKAGES_UNSUSPENDED = "android.intent.action.PACKAGES_UNSUSPENDED";
field public static final java.lang.String ACTION_PACKAGE_ADDED = "android.intent.action.PACKAGE_ADDED";
@@ -24282,19 +24283,26 @@
package android.media.soundtrigger {
public final class SoundTriggerDetector {
- method public boolean startRecognition();
+ method public boolean startRecognition(int);
method public boolean stopRecognition();
+ field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2
+ field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1
}
- public abstract class SoundTriggerDetector.Callback {
+ public static abstract class SoundTriggerDetector.Callback {
ctor public SoundTriggerDetector.Callback();
method public abstract void onAvailabilityChanged(int);
- method public abstract void onDetected();
+ method public abstract void onDetected(android.media.soundtrigger.SoundTriggerDetector.EventPayload);
method public abstract void onError();
method public abstract void onRecognitionPaused();
method public abstract void onRecognitionResumed();
}
+ public static class SoundTriggerDetector.EventPayload {
+ method public android.media.AudioFormat getCaptureAudioFormat();
+ method public byte[] getTriggerAudio();
+ }
+
public final class SoundTriggerManager {
method public android.media.soundtrigger.SoundTriggerDetector createSoundTriggerDetector(java.util.UUID, android.media.soundtrigger.SoundTriggerDetector.Callback, android.os.Handler);
method public void deleteModel(java.util.UUID);
@@ -26807,7 +26815,6 @@
field public static final int REASON_UNSPECIFIED = -1; // 0xffffffff
field public static final deprecated int REPORT_EVENT_AFTER_BUFFER_FULL = 0; // 0x0
field public static final int REPORT_EVENT_AFTER_EACH_SCAN = 1; // 0x1
- field public static final int REPORT_EVENT_CONTEXT_HUB = 8; // 0x8
field public static final int REPORT_EVENT_FULL_SCAN_RESULT = 2; // 0x2
field public static final int REPORT_EVENT_NO_BATCH = 4; // 0x4
field public static final int WIFI_BAND_24_GHZ = 1; // 0x1
@@ -31713,11 +31720,27 @@
public class StorageManager {
method public java.lang.String getMountedObbPath(java.lang.String);
+ method public android.os.storage.StorageVolume getPrimaryVolume();
+ method public android.os.storage.StorageVolume[] getVolumeList();
method public boolean isObbMounted(java.lang.String);
method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
}
+ public class StorageVolume implements android.os.Parcelable {
+ method public android.content.Intent createAccessIntent(java.lang.String);
+ method public int describeContents();
+ method public java.lang.String getDescription(android.content.Context);
+ method public java.lang.String getState();
+ method public java.lang.String getUuid();
+ method public boolean isEmulated();
+ method public boolean isPrimary();
+ method public boolean isRemovable();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.os.storage.StorageVolume> CREATOR;
+ field public static final java.lang.String EXTRA_STORAGE_VOLUME = "android.os.storage.extra.STORAGE_VOLUME";
+ }
+
}
package android.preference {
@@ -33848,6 +33871,8 @@
}
protected static abstract interface ContactsContract.PhoneLookupColumns {
+ field public static final java.lang.String CONTACT_ID = "contact_id";
+ field public static final java.lang.String DATA_ID = "data_id";
field public static final java.lang.String LABEL = "label";
field public static final java.lang.String NORMALIZED_NUMBER = "normalized_number";
field public static final java.lang.String NUMBER = "number";
@@ -34638,7 +34663,6 @@
field public static final java.lang.String ACTION_SEARCH_SETTINGS = "android.search.action.SEARCH_SETTINGS";
field public static final java.lang.String ACTION_SECURITY_SETTINGS = "android.settings.SECURITY_SETTINGS";
field public static final java.lang.String ACTION_SETTINGS = "android.settings.SETTINGS";
- field public static final java.lang.String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS";
field public static final java.lang.String ACTION_SHOW_REGULATORY_INFO = "android.settings.SHOW_REGULATORY_INFO";
field public static final java.lang.String ACTION_SOUND_SETTINGS = "android.settings.SOUND_SETTINGS";
field public static final java.lang.String ACTION_SYNC_SETTINGS = "android.settings.SYNC_SETTINGS";
@@ -36560,6 +36584,7 @@
method public boolean isDigestsSpecified();
method public boolean isRandomizedEncryptionRequired();
method public boolean isUserAuthenticationRequired();
+ method public boolean isUserAuthenticationValidWhileOnBody();
}
public static final class KeyGenParameterSpec.Builder {
@@ -36582,6 +36607,7 @@
method public android.security.keystore.KeyGenParameterSpec.Builder setRandomizedEncryptionRequired(boolean);
method public android.security.keystore.KeyGenParameterSpec.Builder setSignaturePaddings(java.lang.String...);
method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationRequired(boolean);
+ method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidWhileOnBody(boolean);
method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidityDurationSeconds(int);
}
@@ -36601,6 +36627,7 @@
method public boolean isInsideSecureHardware();
method public boolean isUserAuthenticationRequired();
method public boolean isUserAuthenticationRequirementEnforcedBySecureHardware();
+ method public boolean isUserAuthenticationValidWhileOnBody();
}
public class KeyNotYetValidException extends java.security.InvalidKeyException {
@@ -36663,6 +36690,7 @@
method public boolean isDigestsSpecified();
method public boolean isRandomizedEncryptionRequired();
method public boolean isUserAuthenticationRequired();
+ method public boolean isUserAuthenticationValidWhileOnBody();
}
public static final class KeyProtection.Builder {
@@ -36678,6 +36706,7 @@
method public android.security.keystore.KeyProtection.Builder setRandomizedEncryptionRequired(boolean);
method public android.security.keystore.KeyProtection.Builder setSignaturePaddings(java.lang.String...);
method public android.security.keystore.KeyProtection.Builder setUserAuthenticationRequired(boolean);
+ method public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidWhileOnBody(boolean);
method public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidityDurationSeconds(int);
}
@@ -40353,6 +40382,7 @@
method public java.lang.ClassLoader getClassLoader();
method public java.io.File getCodeCacheDir();
method public android.content.ContentResolver getContentResolver();
+ method public java.io.File getDataDir();
method public java.io.File getDatabasePath(java.lang.String);
method public java.io.File getDir(java.lang.String, int);
method public java.io.File getExternalCacheDir();
diff --git a/api/test-current.txt b/api/test-current.txt
index 74c6787..8fcb9bd 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -8009,6 +8009,7 @@
method public final int getColor(int);
method public final android.content.res.ColorStateList getColorStateList(int);
method public abstract android.content.ContentResolver getContentResolver();
+ method public abstract java.io.File getDataDir();
method public abstract java.io.File getDatabasePath(java.lang.String);
method public abstract java.io.File getDir(java.lang.String, int);
method public final android.graphics.drawable.Drawable getDrawable(int);
@@ -8202,6 +8203,7 @@
method public java.lang.ClassLoader getClassLoader();
method public java.io.File getCodeCacheDir();
method public android.content.ContentResolver getContentResolver();
+ method public java.io.File getDataDir();
method public java.io.File getDatabasePath(java.lang.String);
method public java.io.File getDir(java.lang.String, int);
method public java.io.File getExternalCacheDir();
@@ -8539,7 +8541,6 @@
field public static final java.lang.String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL";
field public static final java.lang.String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT";
field public static final java.lang.String ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE";
- field public static final java.lang.String ACTION_OPEN_EXTERNAL_DIRECTORY = "android.intent.action.OPEN_EXTERNAL_DIRECTORY";
field public static final java.lang.String ACTION_PACKAGES_SUSPENDED = "android.intent.action.PACKAGES_SUSPENDED";
field public static final java.lang.String ACTION_PACKAGES_UNSUSPENDED = "android.intent.action.PACKAGES_UNSUSPENDED";
field public static final java.lang.String ACTION_PACKAGE_ADDED = "android.intent.action.PACKAGE_ADDED";
@@ -29375,11 +29376,27 @@
public class StorageManager {
method public java.lang.String getMountedObbPath(java.lang.String);
+ method public android.os.storage.StorageVolume getPrimaryVolume();
+ method public android.os.storage.StorageVolume[] getVolumeList();
method public boolean isObbMounted(java.lang.String);
method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
}
+ public class StorageVolume implements android.os.Parcelable {
+ method public android.content.Intent createAccessIntent(java.lang.String);
+ method public int describeContents();
+ method public java.lang.String getDescription(android.content.Context);
+ method public java.lang.String getState();
+ method public java.lang.String getUuid();
+ method public boolean isEmulated();
+ method public boolean isPrimary();
+ method public boolean isRemovable();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.os.storage.StorageVolume> CREATOR;
+ field public static final java.lang.String EXTRA_STORAGE_VOLUME = "android.os.storage.extra.STORAGE_VOLUME";
+ }
+
}
package android.preference {
@@ -31482,6 +31499,8 @@
}
protected static abstract interface ContactsContract.PhoneLookupColumns {
+ field public static final java.lang.String CONTACT_ID = "contact_id";
+ field public static final java.lang.String DATA_ID = "data_id";
field public static final java.lang.String LABEL = "label";
field public static final java.lang.String NORMALIZED_NUMBER = "normalized_number";
field public static final java.lang.String NUMBER = "number";
@@ -32170,7 +32189,6 @@
field public static final java.lang.String ACTION_SEARCH_SETTINGS = "android.search.action.SEARCH_SETTINGS";
field public static final java.lang.String ACTION_SECURITY_SETTINGS = "android.settings.SECURITY_SETTINGS";
field public static final java.lang.String ACTION_SETTINGS = "android.settings.SETTINGS";
- field public static final java.lang.String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS";
field public static final java.lang.String ACTION_SHOW_REGULATORY_INFO = "android.settings.SHOW_REGULATORY_INFO";
field public static final java.lang.String ACTION_SOUND_SETTINGS = "android.settings.SOUND_SETTINGS";
field public static final java.lang.String ACTION_SYNC_SETTINGS = "android.settings.SYNC_SETTINGS";
@@ -34092,6 +34110,7 @@
method public boolean isDigestsSpecified();
method public boolean isRandomizedEncryptionRequired();
method public boolean isUserAuthenticationRequired();
+ method public boolean isUserAuthenticationValidWhileOnBody();
}
public static final class KeyGenParameterSpec.Builder {
@@ -34114,6 +34133,7 @@
method public android.security.keystore.KeyGenParameterSpec.Builder setRandomizedEncryptionRequired(boolean);
method public android.security.keystore.KeyGenParameterSpec.Builder setSignaturePaddings(java.lang.String...);
method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationRequired(boolean);
+ method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidWhileOnBody(boolean);
method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidityDurationSeconds(int);
}
@@ -34133,6 +34153,7 @@
method public boolean isInsideSecureHardware();
method public boolean isUserAuthenticationRequired();
method public boolean isUserAuthenticationRequirementEnforcedBySecureHardware();
+ method public boolean isUserAuthenticationValidWhileOnBody();
}
public class KeyNotYetValidException extends java.security.InvalidKeyException {
@@ -34195,6 +34216,7 @@
method public boolean isDigestsSpecified();
method public boolean isRandomizedEncryptionRequired();
method public boolean isUserAuthenticationRequired();
+ method public boolean isUserAuthenticationValidWhileOnBody();
}
public static final class KeyProtection.Builder {
@@ -34210,6 +34232,7 @@
method public android.security.keystore.KeyProtection.Builder setRandomizedEncryptionRequired(boolean);
method public android.security.keystore.KeyProtection.Builder setSignaturePaddings(java.lang.String...);
method public android.security.keystore.KeyProtection.Builder setUserAuthenticationRequired(boolean);
+ method public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidWhileOnBody(boolean);
method public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidityDurationSeconds(int);
}
@@ -37626,6 +37649,7 @@
method public java.lang.ClassLoader getClassLoader();
method public java.io.File getCodeCacheDir();
method public android.content.ContentResolver getContentResolver();
+ method public java.io.File getDataDir();
method public java.io.File getDatabasePath(java.lang.String);
method public java.io.File getDir(java.lang.String, int);
method public java.io.File getExternalCacheDir();
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 3385a17..980329f 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -807,8 +807,6 @@
}
/**
- * AnimatorSet is only reversible when the set contains no sequential animation, and no child
- * animators have a start delay.
* @hide
*/
@Override
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index ea58e29..e3adbda 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -49,6 +49,7 @@
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@@ -3988,8 +3989,12 @@
a.recycle();
if (colorPrimary != 0) {
ActivityManager.TaskDescription td = new ActivityManager.TaskDescription();
- td.setPrimaryColor(colorPrimary);
- td.setBackgroundColor(colorBg);
+ if (Color.alpha(colorPrimary) == 0xFF) {
+ td.setPrimaryColor(colorPrimary);
+ }
+ if (Color.alpha(colorBg) == 0xFF) {
+ td.setBackgroundColor(colorBg);
+ }
setTaskDescription(td);
}
}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index ab7d708..91eabcc 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -21,6 +21,7 @@
import android.annotation.Nullable;
import android.annotation.StringRes;
import android.annotation.XmlRes;
+import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Intent;
@@ -1759,7 +1760,7 @@
return candidates;
}
- private static boolean isPackageCandidateVolume(
+ private boolean isPackageCandidateVolume(
ContextImpl context, ApplicationInfo app, VolumeInfo vol) {
final boolean forceAllowOnExternal = Settings.Global.getInt(
context.getContentResolver(), Settings.Global.FORCE_ALLOW_ON_EXTERNAL, 0) != 0;
@@ -1789,6 +1790,15 @@
return app.isInternal();
}
+ // Some apps can't be moved. (e.g. device admins)
+ try {
+ if (mPM.isPackageDeviceAdminOnAnyUser(app.packageName)) {
+ return false;
+ }
+ } catch (RemoteException e) {
+ throw new RuntimeException("Package manager has died", e);
+ }
+
// Otherwise we can move to any private volume
return (vol.getType() == VolumeInfo.TYPE_PRIVATE);
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 5e8d190..8884949 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -460,7 +460,7 @@
private File getPreferencesDir() {
synchronized (mSync) {
if (mPreferencesDir == null) {
- mPreferencesDir = new File(getDataDirFile(), "shared_prefs");
+ mPreferencesDir = new File(getDataDir(), "shared_prefs");
}
return ensurePrivateDirExists(mPreferencesDir);
}
@@ -525,7 +525,7 @@
public File getFilesDir() {
synchronized (mSync) {
if (mFilesDir == null) {
- mFilesDir = new File(getDataDirFile(), "files");
+ mFilesDir = new File(getDataDir(), "files");
}
return ensurePrivateDirExists(mFilesDir);
}
@@ -535,7 +535,7 @@
public File getNoBackupFilesDir() {
synchronized (mSync) {
if (mNoBackupFilesDir == null) {
- mNoBackupFilesDir = new File(getDataDirFile(), "no_backup");
+ mNoBackupFilesDir = new File(getDataDir(), "no_backup");
}
return ensurePrivateDirExists(mNoBackupFilesDir);
}
@@ -587,7 +587,7 @@
public File getCacheDir() {
synchronized (mSync) {
if (mCacheDir == null) {
- mCacheDir = new File(getDataDirFile(), "cache");
+ mCacheDir = new File(getDataDir(), "cache");
}
return ensurePrivateDirExists(mCacheDir);
}
@@ -597,7 +597,7 @@
public File getCodeCacheDir() {
synchronized (mSync) {
if (mCodeCacheDir == null) {
- mCodeCacheDir = new File(getDataDirFile(), "code_cache");
+ mCodeCacheDir = new File(getDataDir(), "code_cache");
}
return ensurePrivateDirExists(mCodeCacheDir);
}
@@ -724,7 +724,7 @@
if ("android".equals(getPackageName())) {
mDatabasesDir = new File("/data/system");
} else {
- mDatabasesDir = new File(getDataDirFile(), "databases");
+ mDatabasesDir = new File(getDataDir(), "databases");
}
}
return ensurePrivateDirExists(mDatabasesDir);
@@ -1920,7 +1920,8 @@
return mDisplayAdjustments;
}
- private File getDataDirFile() {
+ @Override
+ public File getDataDir() {
if (mPackageInfo != null) {
File res = null;
if (isCredentialEncryptedStorage()) {
@@ -1947,7 +1948,7 @@
public File getDir(String name, int mode) {
checkMode(mode);
name = "app_" + name;
- File file = makeFilename(getDataDirFile(), name);
+ File file = makeFilename(getDataDir(), name);
if (!file.exists()) {
file.mkdir();
setFilePermissionsFromMode(file.getPath(), mode,
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 35b7c39..52631d1 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -263,8 +263,8 @@
* The view that will represent this notification in the notification list (which is pulled
* down from the status bar).
*
- * As of N, this field is not used. The notification view is determined by the inputs to
- * {@link Notification.Builder}; a custom RemoteViews can optionally be
+ * As of N, this field may be null. The notification view is determined by the inputs
+ * to {@link Notification.Builder}; a custom RemoteViews can optionally be
* supplied with {@link Notification.Builder#setCustomContentView(RemoteViews)}.
*/
@Deprecated
@@ -275,7 +275,7 @@
* opportunity to show more detail. The system UI may choose to show this
* instead of the normal content view at its discretion.
*
- * As of N, this field is not used. The expanded notification view is determined by the
+ * As of N, this field may be null. The expanded notification view is determined by the
* inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be
* supplied with {@link Notification.Builder#setCustomBigContentView(RemoteViews)}.
*/
@@ -289,7 +289,7 @@
* choose to show this as a heads-up notification, which will pop up so the user can see
* it without leaving their current activity.
*
- * As of N, this field is not used. The heads-up notification view is determined by the
+ * As of N, this field may be null. The heads-up notification view is determined by the
* inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be
* supplied with {@link Notification.Builder#setCustomHeadsUpContentView(RemoteViews)}.
*/
@@ -2129,8 +2129,23 @@
* </pre>
*/
public static class Builder {
+ /**
+ * @hide
+ */
+ public static final String EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT =
+ "android.rebuild.contentViewActionCount";
+ /**
+ * @hide
+ */
+ public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT
+ = "android.rebuild.bigViewActionCount";
+ /**
+ * @hide
+ */
+ public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT
+ = "android.rebuild.hudViewActionCount";
+
private static final int MAX_ACTION_BUTTONS = 3;
- private static final float LARGE_TEXT_SCALE = 1.3f;
private Context mContext;
private Notification mN;
@@ -3566,19 +3581,6 @@
return null;
}
- private void setBuilderContentView(Notification n, RemoteViews contentView) {
- n.contentView = contentView;
- }
-
- private void setBuilderBigContentView(Notification n, RemoteViews bigContentView) {
- n.bigContentView = bigContentView;
- }
-
- private void setBuilderHeadsUpContentView(Notification n,
- RemoteViews headsUpContentView) {
- n.headsUpContentView = headsUpContentView;
- }
-
/**
* @deprecated Use {@link #build()} instead.
*/
@@ -3606,6 +3608,28 @@
mStyle.buildStyled(mN);
}
+ if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
+ if (mN.contentView == null) {
+ mN.contentView = makeContentView();
+ mN.extras.putInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT,
+ mN.contentView.getSequenceNumber());
+ }
+ if (mN.bigContentView == null) {
+ mN.bigContentView = makeBigContentView();
+ if (mN.bigContentView != null) {
+ mN.extras.putInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT,
+ mN.bigContentView.getSequenceNumber());
+ }
+ }
+ if (mN.headsUpContentView == null) {
+ mN.headsUpContentView = makeHeadsUpContentView();
+ if (mN.headsUpContentView != null) {
+ mN.extras.putInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT,
+ mN.headsUpContentView.getSequenceNumber());
+ }
+ }
+ }
+
if ((mN.defaults & DEFAULT_LIGHTS) != 0) {
mN.flags |= FLAG_SHOW_LIGHTS;
}
@@ -3623,6 +3647,40 @@
return n;
}
+ /**
+ * @hide
+ */
+ public static void stripForDelivery(Notification n) {
+ String templateClass = n.extras.getString(EXTRA_TEMPLATE);
+ if (TextUtils.isEmpty(templateClass)) {
+ return;
+ }
+ // Only strip views for known Styles because we won't know how to
+ // re-create them otherwise.
+ if (getNotificationStyleClass(templateClass) == null) {
+ return;
+ }
+ // Get rid of unmodified BuilderRemoteViews.
+ if (n.contentView instanceof BuilderRemoteViews &&
+ n.extras.getInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, -1) ==
+ n.contentView.getSequenceNumber()) {
+ n.contentView = null;
+ n.extras.remove(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT);
+ }
+ if (n.bigContentView instanceof BuilderRemoteViews &&
+ n.extras.getInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, -1) ==
+ n.bigContentView.getSequenceNumber()) {
+ n.bigContentView = null;
+ n.extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT);
+ }
+ if (n.headsUpContentView instanceof BuilderRemoteViews &&
+ n.extras.getInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, -1) ==
+ n.headsUpContentView.getSequenceNumber()) {
+ n.headsUpContentView = null;
+ n.extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT);
+ }
+ }
+
private int getBaseLayoutResource() {
return R.layout.notification_template_material_base;
}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 1f17024..ff2cfd6 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -246,6 +246,7 @@
}
if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
final Notification copy = notification.clone();
+ Builder.stripForDelivery(copy);
try {
service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
copy, idOut, user.getIdentifier());
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 5b8e09c..7e7c5ec 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1033,9 +1033,18 @@
* @hide
*/
public boolean packageHasActiveAdmins(String packageName) {
+ return packageHasActiveAdmins(packageName, myUserId());
+ }
+
+ /**
+ * Used by package administration code to determine if a package can be stopped
+ * or uninstalled.
+ * @hide
+ */
+ public boolean packageHasActiveAdmins(String packageName, int userId) {
if (mService != null) {
try {
- return mService.packageHasActiveAdmins(packageName, myUserId());
+ return mService.packageHasActiveAdmins(packageName, userId);
} catch (RemoteException e) {
Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e);
}
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index 63f1425..aeb3156 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -308,14 +308,31 @@
final String packageName = getPackageName();
final ApplicationInfo appInfo = getApplicationInfo();
- String rootDir = new File(appInfo.dataDir).getCanonicalPath();
- String filesDir = getFilesDir().getCanonicalPath();
- String nobackupDir = getNoBackupFilesDir().getCanonicalPath();
- String databaseDir = getDatabasePath("foo").getParentFile().getCanonicalPath();
- String sharedPrefsDir = getSharedPrefsFile("foo").getParentFile().getCanonicalPath();
- String cacheDir = getCacheDir().getCanonicalPath();
- String codeCacheDir = getCodeCacheDir().getCanonicalPath();
- String libDir = (appInfo.nativeLibraryDir != null)
+ // System apps have control over where their default storage context
+ // is pointed, so we're always explicit when building paths.
+ final Context ceContext = createCredentialEncryptedStorageContext();
+ final String rootDir = ceContext.getDataDir().getCanonicalPath();
+ final String filesDir = ceContext.getFilesDir().getCanonicalPath();
+ final String noBackupDir = ceContext.getNoBackupFilesDir().getCanonicalPath();
+ final String databaseDir = ceContext.getDatabasePath("foo").getParentFile()
+ .getCanonicalPath();
+ final String sharedPrefsDir = ceContext.getSharedPreferencesPath("foo").getParentFile()
+ .getCanonicalPath();
+ final String cacheDir = ceContext.getCacheDir().getCanonicalPath();
+ final String codeCacheDir = ceContext.getCodeCacheDir().getCanonicalPath();
+
+ final Context deContext = createDeviceEncryptedStorageContext();
+ final String deviceRootDir = deContext.getDataDir().getCanonicalPath();
+ final String deviceFilesDir = deContext.getFilesDir().getCanonicalPath();
+ final String deviceNoBackupDir = deContext.getNoBackupFilesDir().getCanonicalPath();
+ final String deviceDatabaseDir = deContext.getDatabasePath("foo").getParentFile()
+ .getCanonicalPath();
+ final String deviceSharedPrefsDir = deContext.getSharedPreferencesPath("foo")
+ .getParentFile().getCanonicalPath();
+ final String deviceCacheDir = deContext.getCacheDir().getCanonicalPath();
+ final String deviceCodeCacheDir = deContext.getCodeCacheDir().getCanonicalPath();
+
+ final String libDir = (appInfo.nativeLibraryDir != null)
? new File(appInfo.nativeLibraryDir).getCanonicalPath()
: null;
@@ -325,30 +342,48 @@
final ArraySet<String> traversalExcludeSet = new ArraySet<String>();
// Add the directories we always exclude.
+ traversalExcludeSet.add(filesDir);
+ traversalExcludeSet.add(noBackupDir);
+ traversalExcludeSet.add(databaseDir);
+ traversalExcludeSet.add(sharedPrefsDir);
traversalExcludeSet.add(cacheDir);
traversalExcludeSet.add(codeCacheDir);
- traversalExcludeSet.add(nobackupDir);
+
+ traversalExcludeSet.add(deviceFilesDir);
+ traversalExcludeSet.add(deviceNoBackupDir);
+ traversalExcludeSet.add(deviceDatabaseDir);
+ traversalExcludeSet.add(deviceSharedPrefsDir);
+ traversalExcludeSet.add(deviceCacheDir);
+ traversalExcludeSet.add(deviceCodeCacheDir);
+
if (libDir != null) {
traversalExcludeSet.add(libDir);
}
- traversalExcludeSet.add(databaseDir);
- traversalExcludeSet.add(sharedPrefsDir);
- traversalExcludeSet.add(filesDir);
-
// Root dir first.
applyXmlFiltersAndDoFullBackupForDomain(
packageName, FullBackup.ROOT_TREE_TOKEN, manifestIncludeMap,
manifestExcludeSet, traversalExcludeSet, data);
traversalExcludeSet.add(rootDir);
+ applyXmlFiltersAndDoFullBackupForDomain(
+ packageName, FullBackup.DEVICE_ROOT_TREE_TOKEN, manifestIncludeMap,
+ manifestExcludeSet, traversalExcludeSet, data);
+ traversalExcludeSet.add(deviceRootDir);
+
// Data dir next.
traversalExcludeSet.remove(filesDir);
applyXmlFiltersAndDoFullBackupForDomain(
- packageName, FullBackup.DATA_TREE_TOKEN, manifestIncludeMap,
+ packageName, FullBackup.FILES_TREE_TOKEN, manifestIncludeMap,
manifestExcludeSet, traversalExcludeSet, data);
traversalExcludeSet.add(filesDir);
+ traversalExcludeSet.remove(deviceFilesDir);
+ applyXmlFiltersAndDoFullBackupForDomain(
+ packageName, FullBackup.DEVICE_FILES_TREE_TOKEN, manifestIncludeMap,
+ manifestExcludeSet, traversalExcludeSet, data);
+ traversalExcludeSet.add(deviceFilesDir);
+
// Database directory.
traversalExcludeSet.remove(databaseDir);
applyXmlFiltersAndDoFullBackupForDomain(
@@ -356,6 +391,12 @@
manifestExcludeSet, traversalExcludeSet, data);
traversalExcludeSet.add(databaseDir);
+ traversalExcludeSet.remove(deviceDatabaseDir);
+ applyXmlFiltersAndDoFullBackupForDomain(
+ packageName, FullBackup.DEVICE_DATABASE_TREE_TOKEN, manifestIncludeMap,
+ manifestExcludeSet, traversalExcludeSet, data);
+ traversalExcludeSet.add(deviceDatabaseDir);
+
// SharedPrefs.
traversalExcludeSet.remove(sharedPrefsDir);
applyXmlFiltersAndDoFullBackupForDomain(
@@ -363,6 +404,12 @@
manifestExcludeSet, traversalExcludeSet, data);
traversalExcludeSet.add(sharedPrefsDir);
+ traversalExcludeSet.remove(deviceSharedPrefsDir);
+ applyXmlFiltersAndDoFullBackupForDomain(
+ packageName, FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN, manifestIncludeMap,
+ manifestExcludeSet, traversalExcludeSet, data);
+ traversalExcludeSet.add(deviceSharedPrefsDir);
+
// getExternalFilesDir() location associated with this app. Technically there should
// not be any files here if the app does not properly have permission to access
// external storage, but edge cases happen. fullBackupFileTree() catches
@@ -445,27 +492,49 @@
*/
public final void fullBackupFile(File file, FullBackupDataOutput output) {
// Look up where all of our various well-defined dir trees live on this device
- String mainDir;
- String filesDir;
- String nbFilesDir;
- String dbDir;
- String spDir;
- String cacheDir;
- String codeCacheDir;
- String libDir;
+ final String rootDir;
+ final String filesDir;
+ final String nbFilesDir;
+ final String dbDir;
+ final String spDir;
+ final String cacheDir;
+ final String codeCacheDir;
+ final String deviceRootDir;
+ final String deviceFilesDir;
+ final String deviceNbFilesDir;
+ final String deviceDbDir;
+ final String deviceSpDir;
+ final String deviceCacheDir;
+ final String deviceCodeCacheDir;
+ final String libDir;
+
String efDir = null;
String filePath;
ApplicationInfo appInfo = getApplicationInfo();
try {
- mainDir = new File(appInfo.dataDir).getCanonicalPath();
- filesDir = getFilesDir().getCanonicalPath();
- nbFilesDir = getNoBackupFilesDir().getCanonicalPath();
- dbDir = getDatabasePath("foo").getParentFile().getCanonicalPath();
- spDir = getSharedPrefsFile("foo").getParentFile().getCanonicalPath();
- cacheDir = getCacheDir().getCanonicalPath();
- codeCacheDir = getCodeCacheDir().getCanonicalPath();
+ // System apps have control over where their default storage context
+ // is pointed, so we're always explicit when building paths.
+ final Context ceContext = createCredentialEncryptedStorageContext();
+ rootDir = ceContext.getDataDir().getCanonicalPath();
+ filesDir = ceContext.getFilesDir().getCanonicalPath();
+ nbFilesDir = ceContext.getNoBackupFilesDir().getCanonicalPath();
+ dbDir = ceContext.getDatabasePath("foo").getParentFile().getCanonicalPath();
+ spDir = ceContext.getSharedPreferencesPath("foo").getParentFile().getCanonicalPath();
+ cacheDir = ceContext.getCacheDir().getCanonicalPath();
+ codeCacheDir = ceContext.getCodeCacheDir().getCanonicalPath();
+
+ final Context deContext = createDeviceEncryptedStorageContext();
+ deviceRootDir = deContext.getDataDir().getCanonicalPath();
+ deviceFilesDir = deContext.getFilesDir().getCanonicalPath();
+ deviceNbFilesDir = deContext.getNoBackupFilesDir().getCanonicalPath();
+ deviceDbDir = deContext.getDatabasePath("foo").getParentFile().getCanonicalPath();
+ deviceSpDir = deContext.getSharedPreferencesPath("foo").getParentFile()
+ .getCanonicalPath();
+ deviceCacheDir = deContext.getCacheDir().getCanonicalPath();
+ deviceCodeCacheDir = deContext.getCodeCacheDir().getCanonicalPath();
+
libDir = (appInfo.nativeLibraryDir == null)
? null
: new File(appInfo.nativeLibraryDir).getCanonicalPath();
@@ -489,8 +558,11 @@
if (filePath.startsWith(cacheDir)
|| filePath.startsWith(codeCacheDir)
- || filePath.startsWith(libDir)
- || filePath.startsWith(nbFilesDir)) {
+ || filePath.startsWith(nbFilesDir)
+ || filePath.startsWith(deviceCacheDir)
+ || filePath.startsWith(deviceCodeCacheDir)
+ || filePath.startsWith(deviceNbFilesDir)
+ || filePath.startsWith(libDir)) {
Log.w(TAG, "lib, cache, code_cache, and no_backup files are not backed up");
return;
}
@@ -504,11 +576,23 @@
domain = FullBackup.SHAREDPREFS_TREE_TOKEN;
rootpath = spDir;
} else if (filePath.startsWith(filesDir)) {
- domain = FullBackup.DATA_TREE_TOKEN;
+ domain = FullBackup.FILES_TREE_TOKEN;
rootpath = filesDir;
- } else if (filePath.startsWith(mainDir)) {
+ } else if (filePath.startsWith(rootDir)) {
domain = FullBackup.ROOT_TREE_TOKEN;
- rootpath = mainDir;
+ rootpath = rootDir;
+ } else if (filePath.startsWith(deviceDbDir)) {
+ domain = FullBackup.DEVICE_DATABASE_TREE_TOKEN;
+ rootpath = deviceDbDir;
+ } else if (filePath.startsWith(deviceSpDir)) {
+ domain = FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN;
+ rootpath = deviceSpDir;
+ } else if (filePath.startsWith(deviceFilesDir)) {
+ domain = FullBackup.DEVICE_FILES_TREE_TOKEN;
+ rootpath = deviceFilesDir;
+ } else if (filePath.startsWith(deviceRootDir)) {
+ domain = FullBackup.DEVICE_ROOT_TREE_TOKEN;
+ rootpath = deviceRootDir;
} else if ((efDir != null) && filePath.startsWith(efDir)) {
domain = FullBackup.MANAGED_EXTERNAL_TREE_TOKEN;
rootpath = efDir;
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
index 9ea2ba2..cdc80e3 100644
--- a/core/java/android/app/backup/FullBackup.java
+++ b/core/java/android/app/backup/FullBackup.java
@@ -55,13 +55,22 @@
public static final String APK_TREE_TOKEN = "a";
public static final String OBB_TREE_TOKEN = "obb";
+
public static final String ROOT_TREE_TOKEN = "r";
- public static final String DATA_TREE_TOKEN = "f";
+ public static final String FILES_TREE_TOKEN = "f";
public static final String NO_BACKUP_TREE_TOKEN = "nb";
public static final String DATABASE_TREE_TOKEN = "db";
public static final String SHAREDPREFS_TREE_TOKEN = "sp";
- public static final String MANAGED_EXTERNAL_TREE_TOKEN = "ef";
public static final String CACHE_TREE_TOKEN = "c";
+
+ public static final String DEVICE_ROOT_TREE_TOKEN = "d_r";
+ public static final String DEVICE_FILES_TREE_TOKEN = "d_f";
+ public static final String DEVICE_NO_BACKUP_TREE_TOKEN = "d_nb";
+ public static final String DEVICE_DATABASE_TREE_TOKEN = "d_db";
+ public static final String DEVICE_SHAREDPREFS_TREE_TOKEN = "d_sp";
+ public static final String DEVICE_CACHE_TREE_TOKEN = "d_c";
+
+ public static final String MANAGED_EXTERNAL_TREE_TOKEN = "ef";
public static final String SHARED_STORAGE_TOKEN = "shared";
public static final String APPS_PREFIX = "apps/";
@@ -201,10 +210,18 @@
private final File DATABASE_DIR;
private final File ROOT_DIR;
private final File SHAREDPREF_DIR;
- private final File EXTERNAL_DIR;
private final File CACHE_DIR;
private final File NOBACKUP_DIR;
+ private final File DEVICE_FILES_DIR;
+ private final File DEVICE_DATABASE_DIR;
+ private final File DEVICE_ROOT_DIR;
+ private final File DEVICE_SHAREDPREF_DIR;
+ private final File DEVICE_CACHE_DIR;
+ private final File DEVICE_NOBACKUP_DIR;
+
+ private final File EXTERNAL_DIR;
+
final int mFullBackupContent;
final PackageManager mPackageManager;
final String mPackageName;
@@ -214,7 +231,7 @@
*/
String tokenToDirectoryPath(String domainToken) {
try {
- if (domainToken.equals(FullBackup.DATA_TREE_TOKEN)) {
+ if (domainToken.equals(FullBackup.FILES_TREE_TOKEN)) {
return FILES_DIR.getCanonicalPath();
} else if (domainToken.equals(FullBackup.DATABASE_TREE_TOKEN)) {
return DATABASE_DIR.getCanonicalPath();
@@ -224,14 +241,26 @@
return SHAREDPREF_DIR.getCanonicalPath();
} else if (domainToken.equals(FullBackup.CACHE_TREE_TOKEN)) {
return CACHE_DIR.getCanonicalPath();
+ } else if (domainToken.equals(FullBackup.NO_BACKUP_TREE_TOKEN)) {
+ return NOBACKUP_DIR.getCanonicalPath();
+ } else if (domainToken.equals(FullBackup.DEVICE_FILES_TREE_TOKEN)) {
+ return DEVICE_FILES_DIR.getCanonicalPath();
+ } else if (domainToken.equals(FullBackup.DEVICE_DATABASE_TREE_TOKEN)) {
+ return DEVICE_DATABASE_DIR.getCanonicalPath();
+ } else if (domainToken.equals(FullBackup.DEVICE_ROOT_TREE_TOKEN)) {
+ return DEVICE_ROOT_DIR.getCanonicalPath();
+ } else if (domainToken.equals(FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN)) {
+ return DEVICE_SHAREDPREF_DIR.getCanonicalPath();
+ } else if (domainToken.equals(FullBackup.DEVICE_CACHE_TREE_TOKEN)) {
+ return DEVICE_CACHE_DIR.getCanonicalPath();
+ } else if (domainToken.equals(FullBackup.DEVICE_NO_BACKUP_TREE_TOKEN)) {
+ return DEVICE_NOBACKUP_DIR.getCanonicalPath();
} else if (domainToken.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) {
if (EXTERNAL_DIR != null) {
return EXTERNAL_DIR.getCanonicalPath();
} else {
return null;
}
- } else if (domainToken.equals(FullBackup.NO_BACKUP_TREE_TOKEN)) {
- return NOBACKUP_DIR.getCanonicalPath();
}
// Not a supported location
Log.i(TAG, "Unrecognized domain " + domainToken);
@@ -257,12 +286,25 @@
mFullBackupContent = context.getApplicationInfo().fullBackupContent;
mPackageManager = context.getPackageManager();
mPackageName = context.getPackageName();
- FILES_DIR = context.getFilesDir();
- DATABASE_DIR = context.getDatabasePath("foo").getParentFile();
- ROOT_DIR = new File(context.getApplicationInfo().dataDir);
- SHAREDPREF_DIR = context.getSharedPrefsFile("foo").getParentFile();
- CACHE_DIR = context.getCacheDir();
- NOBACKUP_DIR = context.getNoBackupFilesDir();
+
+ // System apps have control over where their default storage context
+ // is pointed, so we're always explicit when building paths.
+ final Context ceContext = context.createCredentialEncryptedStorageContext();
+ FILES_DIR = ceContext.getFilesDir();
+ DATABASE_DIR = ceContext.getDatabasePath("foo").getParentFile();
+ ROOT_DIR = ceContext.getDataDir();
+ SHAREDPREF_DIR = ceContext.getSharedPreferencesPath("foo").getParentFile();
+ CACHE_DIR = ceContext.getCacheDir();
+ NOBACKUP_DIR = ceContext.getNoBackupFilesDir();
+
+ final Context deContext = context.createDeviceEncryptedStorageContext();
+ DEVICE_FILES_DIR = deContext.getFilesDir();
+ DEVICE_DATABASE_DIR = deContext.getDatabasePath("foo").getParentFile();
+ DEVICE_ROOT_DIR = deContext.getDataDir();
+ DEVICE_SHAREDPREF_DIR = deContext.getSharedPreferencesPath("foo").getParentFile();
+ DEVICE_CACHE_DIR = deContext.getCacheDir();
+ DEVICE_NOBACKUP_DIR = deContext.getNoBackupFilesDir();
+
if (android.os.Process.myUid() != Process.SYSTEM_UID) {
EXTERNAL_DIR = context.getExternalFilesDir(null);
} else {
@@ -403,6 +445,13 @@
Log.v(TAG_XML_PARSER, "...automatically generated "
+ canonicalJournalPath + ". Ignore if nonexistent.");
}
+ final String canonicalWalPath =
+ canonicalFile.getCanonicalPath() + "-wal";
+ activeSet.add(canonicalWalPath);
+ if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
+ Log.v(TAG_XML_PARSER, "...automatically generated "
+ + canonicalWalPath + ". Ignore if nonexistent.");
+ }
}
// Special case for sharedpref files (not dirs) also add ".xml" suffix file.
@@ -485,11 +534,19 @@
if ("root".equals(xmlDomain)) {
return FullBackup.ROOT_TREE_TOKEN;
} else if ("file".equals(xmlDomain)) {
- return FullBackup.DATA_TREE_TOKEN;
+ return FullBackup.FILES_TREE_TOKEN;
} else if ("database".equals(xmlDomain)) {
return FullBackup.DATABASE_TREE_TOKEN;
} else if ("sharedpref".equals(xmlDomain)) {
return FullBackup.SHAREDPREFS_TREE_TOKEN;
+ } else if ("device_root".equals(xmlDomain)) {
+ return FullBackup.DEVICE_ROOT_TREE_TOKEN;
+ } else if ("device_file".equals(xmlDomain)) {
+ return FullBackup.DEVICE_FILES_TREE_TOKEN;
+ } else if ("device_database".equals(xmlDomain)) {
+ return FullBackup.DEVICE_DATABASE_TREE_TOKEN;
+ } else if ("device_sharedpref".equals(xmlDomain)) {
+ return FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN;
} else if ("external".equals(xmlDomain)) {
return FullBackup.MANAGED_EXTERNAL_TREE_TOKEN;
} else {
@@ -542,6 +599,14 @@
return ROOT_DIR;
} else if ("sharedpref".equals(domain)) {
return SHAREDPREF_DIR;
+ } else if ("device_file".equals(domain)) {
+ return DEVICE_FILES_DIR;
+ } else if ("device_database".equals(domain)) {
+ return DEVICE_DATABASE_DIR;
+ } else if ("device_root".equals(domain)) {
+ return DEVICE_ROOT_DIR;
+ } else if ("device_sharedpref".equals(domain)) {
+ return DEVICE_SHAREDPREF_DIR;
} else if ("external".equals(domain)) {
return EXTERNAL_DIR;
} else {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index fff0c14..0cdbef0 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -811,6 +811,25 @@
public abstract File getSharedPreferencesPath(String name);
/**
+ * Returns the absolute path to the directory on the filesystem where all
+ * private files belonging to this app are stored. This is the top-level
+ * directory under which {@link #getFilesDir()}, {@link #getCacheDir()}, etc
+ * are contained. Apps should <em>not</em> create any files or directories
+ * as direct children of this directory, since it's a reserved namespace
+ * belonging to the platform. Instead, use {@link #getDir(String, int)} or
+ * other storage APIs.
+ * <p>
+ * The returned path may change over time if the calling app is moved to an
+ * adopted storage device, so only relative paths should be persisted.
+ * <p>
+ * No additional permissions are required for the calling app to read or
+ * write files under the returned path.
+ *
+ * @see #getDir(String, int)
+ */
+ public abstract File getDataDir();
+
+ /**
* Returns the absolute path to the directory on the filesystem where files
* created with {@link #openFileOutput} are stored.
* <p>
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 61b87a9..323c9bf 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -212,6 +212,11 @@
}
@Override
+ public File getDataDir() {
+ return mBase.getDataDir();
+ }
+
+ @Override
public File getFilesDir() {
return mBase.getFilesDir();
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index b476a25..8f2b9c8 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3187,38 +3187,6 @@
ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE";
/**
- * Activity Action: Give access to a standard storage directory after obtaining the user's
- * approval.
- * <p>
- * When invoked, the system will ask the user to grant access to the requested directory (and
- * its descendants).
- * <p>
- * To gain access to descendant (child, grandchild, etc) documents, use
- * {@link DocumentsContract#buildDocumentUriUsingTree(Uri, String)} and
- * {@link DocumentsContract#buildChildDocumentsUriUsingTree(Uri, String)} with the returned URI.
- * <p>
- * Input: full path to a standard directory, in the form of
- * {@code STORAGE_ROOT + STANDARD_DIRECTORY}, where {@code STORAGE_ROOT} is the physical path of
- * a storage container, and {@code STANDARD_DIRECTORY} is one of
- * {@link Environment#DIRECTORY_MUSIC}, {@link Environment#DIRECTORY_PODCASTS},
- * {@link Environment#DIRECTORY_RINGTONES}, {@link Environment#DIRECTORY_ALARMS},
- * {@link Environment#DIRECTORY_NOTIFICATIONS}, {@link Environment#DIRECTORY_PICTURES},
- * {@link Environment#DIRECTORY_MOVIES}, {@link Environment#DIRECTORY_DOWNLOADS},
- * {@link Environment#DIRECTORY_DCIM}, or {@link Environment#DIRECTORY_DOCUMENTS}
- * <p>
- * For example, to open the "Pictures" folder in the default external storage, the intent's data
- * would be: {@code Uri.fromFile(new File(Environment.getExternalStorageDirectory(),
- * Environment.DIRECTORY_PICTURES))}.
- * <p>
- * Output: The URI representing the requested directory tree.
- *
- * @see DocumentsContract
- */
- @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String
- ACTION_OPEN_EXTERNAL_DIRECTORY = "android.intent.action.OPEN_EXTERNAL_DIRECTORY";
-
- /**
* Broadcast Action: List of dynamic sensor is changed due to new sensor being connected or
* exisiting sensor being disconnected.
*
@@ -8952,7 +8920,6 @@
case ACTION_MEDIA_SCANNER_SCAN_FILE:
case ACTION_PACKAGE_NEEDS_VERIFICATION:
case ACTION_PACKAGE_VERIFIED:
- case ACTION_OPEN_EXTERNAL_DIRECTORY: // TODO: temporary until bug 26742218 is fixed
// Ignore legacy actions
break;
default:
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index ccb5f82..d6b674c 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -537,4 +537,6 @@
boolean setRequiredForSystemUser(String packageName, boolean systemUserApp);
String getServicesSystemSharedLibraryPackageName();
+
+ boolean isPackageDeviceAdminOnAnyUser(String packageName);
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index c9ee4f3..0967608 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1233,6 +1233,14 @@
public static final int MOVE_FAILED_OPERATION_PENDING = -7;
/**
+ * Error code that is passed to the {@link IPackageMoveObserver} if the
+ * specified package cannot be moved since it contains a device admin.
+ *
+ * @hide
+ */
+ public static final int MOVE_FAILED_DEVICE_ADMIN = -8;
+
+ /**
* Flag parameter for {@link #movePackage} to indicate that
* the package should be moved to internal storage if its
* been installed on external media.
diff --git a/core/java/android/net/BaseDhcpStateMachine.java b/core/java/android/net/BaseDhcpStateMachine.java
deleted file mode 100644
index a25847d..0000000
--- a/core/java/android/net/BaseDhcpStateMachine.java
+++ /dev/null
@@ -1,35 +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 android.net;
-
-import com.android.internal.util.StateMachine;
-
-/**
- * Interface that must be implemented by DHCP state machines.
- *
- * This is an abstract class instead of a Java interface so that callers can just declare an object
- * of this type and be able to call all the methods defined by either StateMachine or this class.
- *
- * @hide
- */
-public abstract class BaseDhcpStateMachine extends StateMachine {
- protected BaseDhcpStateMachine(String tag) {
- super(tag);
- }
- public abstract void registerForPreDhcpNotification();
- public abstract void doQuit();
-}
diff --git a/core/java/android/net/DhcpStateMachine.java b/core/java/android/net/DhcpStateMachine.java
deleted file mode 100644
index 73ef78e..0000000
--- a/core/java/android/net/DhcpStateMachine.java
+++ /dev/null
@@ -1,462 +0,0 @@
-/*
- * Copyright (C) 2011 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 com.android.internal.util.Protocol;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.DhcpResults;
-import android.net.NetworkUtils;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.SystemClock;
-import android.util.Log;
-
-/**
- * StateMachine that interacts with the native DHCP client and can talk to
- * a controller that also needs to be a StateMachine
- *
- * The DhcpStateMachine provides the following features:
- * - Wakeup and renewal using the native DHCP client (which will not renew
- * on its own when the device is in suspend state and this can lead to device
- * holding IP address beyond expiry)
- * - A notification right before DHCP request or renewal is started. This
- * can be used for any additional setup before DHCP. For example, wifi sets
- * BT-Wifi coex settings right before DHCP is initiated
- *
- * @hide
- */
-public class DhcpStateMachine extends BaseDhcpStateMachine {
-
- private static final String TAG = "DhcpStateMachine";
- private static final boolean DBG = false;
-
-
- /* A StateMachine that controls the DhcpStateMachine */
- private StateMachine mController;
-
- private Context mContext;
- private BroadcastReceiver mBroadcastReceiver;
- private AlarmManager mAlarmManager;
- private PendingIntent mDhcpRenewalIntent;
- private PowerManager.WakeLock mDhcpRenewWakeLock;
- private static final String WAKELOCK_TAG = "DHCP";
-
- //Remember DHCP configuration from first request
- private DhcpResults mDhcpResults;
-
- private static final int DHCP_RENEW = 0;
- private static final String ACTION_DHCP_RENEW = "android.net.wifi.DHCP_RENEW";
-
- //Used for sanity check on setting up renewal
- private static final int MIN_RENEWAL_TIME_SECS = 5 * 60; // 5 minutes
-
- private final String mInterfaceName;
- private boolean mRegisteredForPreDhcpNotification = false;
-
- private static final int BASE = Protocol.BASE_DHCP;
-
- /* Commands from controller to start/stop DHCP */
- public static final int CMD_START_DHCP = BASE + 1;
- public static final int CMD_STOP_DHCP = BASE + 2;
- public static final int CMD_RENEW_DHCP = BASE + 3;
-
- /* Notification from DHCP state machine prior to DHCP discovery/renewal */
- public static final int CMD_PRE_DHCP_ACTION = BASE + 4;
- /* Notification from DHCP state machine post DHCP discovery/renewal. Indicates
- * success/failure */
- public static final int CMD_POST_DHCP_ACTION = BASE + 5;
- /* Notification from DHCP state machine before quitting */
- public static final int CMD_ON_QUIT = BASE + 6;
-
- /* Command from controller to indicate DHCP discovery/renewal can continue
- * after pre DHCP action is complete */
- public static final int CMD_PRE_DHCP_ACTION_COMPLETE = BASE + 7;
-
- /* Command from ourselves to see if DHCP results are available */
- private static final int CMD_GET_DHCP_RESULTS = BASE + 8;
-
- /* Message.arg1 arguments to CMD_POST_DHCP notification */
- public static final int DHCP_SUCCESS = 1;
- public static final int DHCP_FAILURE = 2;
-
- private State mDefaultState = new DefaultState();
- private State mStoppedState = new StoppedState();
- private State mWaitBeforeStartState = new WaitBeforeStartState();
- private State mRunningState = new RunningState();
- private State mWaitBeforeRenewalState = new WaitBeforeRenewalState();
- private State mPollingState = new PollingState();
-
- private DhcpStateMachine(Context context, StateMachine controller, String intf) {
- super(TAG);
-
- mContext = context;
- mController = controller;
- mInterfaceName = intf;
-
- mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
- Intent dhcpRenewalIntent = new Intent(ACTION_DHCP_RENEW, null);
- mDhcpRenewalIntent = PendingIntent.getBroadcast(mContext, DHCP_RENEW, dhcpRenewalIntent, 0);
-
- PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
- mDhcpRenewWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
- mDhcpRenewWakeLock.setReferenceCounted(false);
-
- mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- //DHCP renew
- if (DBG) Log.d(TAG, "Sending a DHCP renewal " + this);
- //Lock released after 40s in worst case scenario
- mDhcpRenewWakeLock.acquire(40000);
- sendMessage(CMD_RENEW_DHCP);
- }
- };
- mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION_DHCP_RENEW));
-
- addState(mDefaultState);
- addState(mStoppedState, mDefaultState);
- addState(mWaitBeforeStartState, mDefaultState);
- addState(mPollingState, mDefaultState);
- addState(mRunningState, mDefaultState);
- addState(mWaitBeforeRenewalState, mDefaultState);
-
- setInitialState(mStoppedState);
- }
-
- public static DhcpStateMachine makeDhcpStateMachine(Context context, StateMachine controller,
- String intf) {
- DhcpStateMachine dsm = new DhcpStateMachine(context, controller, intf);
- dsm.start();
- return dsm;
- }
-
- /**
- * This sends a notification right before DHCP request/renewal so that the
- * controller can do certain actions before DHCP packets are sent out.
- * When the controller is ready, it sends a CMD_PRE_DHCP_ACTION_COMPLETE message
- * to indicate DHCP can continue
- *
- * This is used by Wifi at this time for the purpose of doing BT-Wifi coex
- * handling during Dhcp
- */
- @Override
- public void registerForPreDhcpNotification() {
- mRegisteredForPreDhcpNotification = true;
- }
-
- /**
- * Quit the DhcpStateMachine.
- *
- * @hide
- */
- @Override
- public void doQuit() {
- quit();
- }
-
- protected void onQuitting() {
- mController.sendMessage(CMD_ON_QUIT);
- }
-
- class DefaultState extends State {
- @Override
- public void exit() {
- mContext.unregisterReceiver(mBroadcastReceiver);
- }
- @Override
- public boolean processMessage(Message message) {
- if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
- switch (message.what) {
- case CMD_RENEW_DHCP:
- Log.e(TAG, "Error! Failed to handle a DHCP renewal on " + mInterfaceName);
- mDhcpRenewWakeLock.release();
- break;
- default:
- Log.e(TAG, "Error! unhandled message " + message);
- break;
- }
- return HANDLED;
- }
- }
-
-
- class StoppedState extends State {
- @Override
- public void enter() {
- if (DBG) Log.d(TAG, getName() + "\n");
- if (!NetworkUtils.stopDhcp(mInterfaceName)) {
- Log.e(TAG, "Failed to stop Dhcp on " + mInterfaceName);
- }
- mDhcpResults = null;
- }
-
- @Override
- public boolean processMessage(Message message) {
- boolean retValue = HANDLED;
- if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
- switch (message.what) {
- case CMD_START_DHCP:
- if (mRegisteredForPreDhcpNotification) {
- /* Notify controller before starting DHCP */
- mController.sendMessage(CMD_PRE_DHCP_ACTION);
- transitionTo(mWaitBeforeStartState);
- } else {
- if (runDhcpStart()) {
- transitionTo(mRunningState);
- }
- }
- break;
- case CMD_STOP_DHCP:
- //ignore
- break;
- default:
- retValue = NOT_HANDLED;
- break;
- }
- return retValue;
- }
- }
-
- class WaitBeforeStartState extends State {
- @Override
- public void enter() {
- if (DBG) Log.d(TAG, getName() + "\n");
- }
-
- @Override
- public boolean processMessage(Message message) {
- boolean retValue = HANDLED;
- if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
- switch (message.what) {
- case CMD_PRE_DHCP_ACTION_COMPLETE:
- if (runDhcpStart()) {
- transitionTo(mRunningState);
- } else {
- transitionTo(mPollingState);
- }
- break;
- case CMD_STOP_DHCP:
- transitionTo(mStoppedState);
- break;
- case CMD_START_DHCP:
- //ignore
- break;
- default:
- retValue = NOT_HANDLED;
- break;
- }
- return retValue;
- }
- }
-
- class PollingState extends State {
- private static final long MAX_DELAY_SECONDS = 32;
- private long delaySeconds;
-
- private void scheduleNextResultsCheck() {
- sendMessageDelayed(obtainMessage(CMD_GET_DHCP_RESULTS), delaySeconds * 1000);
- delaySeconds *= 2;
- if (delaySeconds > MAX_DELAY_SECONDS) {
- delaySeconds = MAX_DELAY_SECONDS;
- }
- }
-
- @Override
- public void enter() {
- if (DBG) Log.d(TAG, "Entering " + getName() + "\n");
- delaySeconds = 1;
- scheduleNextResultsCheck();
- }
-
- @Override
- public boolean processMessage(Message message) {
- boolean retValue = HANDLED;
- if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
- switch (message.what) {
- case CMD_GET_DHCP_RESULTS:
- if (DBG) Log.d(TAG, "GET_DHCP_RESULTS on " + mInterfaceName);
- if (dhcpSucceeded()) {
- transitionTo(mRunningState);
- } else {
- scheduleNextResultsCheck();
- }
- break;
- case CMD_STOP_DHCP:
- transitionTo(mStoppedState);
- break;
- default:
- retValue = NOT_HANDLED;
- break;
- }
- return retValue;
- }
-
- @Override
- public void exit() {
- if (DBG) Log.d(TAG, "Exiting " + getName() + "\n");
- removeMessages(CMD_GET_DHCP_RESULTS);
- }
- }
-
- class RunningState extends State {
- @Override
- public void enter() {
- if (DBG) Log.d(TAG, getName() + "\n");
- }
-
- @Override
- public boolean processMessage(Message message) {
- boolean retValue = HANDLED;
- if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
- switch (message.what) {
- case CMD_STOP_DHCP:
- mAlarmManager.cancel(mDhcpRenewalIntent);
- transitionTo(mStoppedState);
- break;
- case CMD_RENEW_DHCP:
- if (mRegisteredForPreDhcpNotification) {
- /* Notify controller before starting DHCP */
- mController.sendMessage(CMD_PRE_DHCP_ACTION);
- transitionTo(mWaitBeforeRenewalState);
- //mDhcpRenewWakeLock is released in WaitBeforeRenewalState
- } else {
- if (!runDhcpRenew()) {
- transitionTo(mStoppedState);
- }
- mDhcpRenewWakeLock.release();
- }
- break;
- case CMD_START_DHCP:
- //ignore
- break;
- default:
- retValue = NOT_HANDLED;
- }
- return retValue;
- }
- }
-
- class WaitBeforeRenewalState extends State {
- @Override
- public void enter() {
- if (DBG) Log.d(TAG, getName() + "\n");
- }
-
- @Override
- public boolean processMessage(Message message) {
- boolean retValue = HANDLED;
- if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
- switch (message.what) {
- case CMD_STOP_DHCP:
- mAlarmManager.cancel(mDhcpRenewalIntent);
- transitionTo(mStoppedState);
- break;
- case CMD_PRE_DHCP_ACTION_COMPLETE:
- if (runDhcpRenew()) {
- transitionTo(mRunningState);
- } else {
- transitionTo(mStoppedState);
- }
- break;
- case CMD_START_DHCP:
- //ignore
- break;
- default:
- retValue = NOT_HANDLED;
- break;
- }
- return retValue;
- }
- @Override
- public void exit() {
- mDhcpRenewWakeLock.release();
- }
- }
-
- private boolean dhcpSucceeded() {
- DhcpResults dhcpResults = new DhcpResults();
- if (!NetworkUtils.getDhcpResults(mInterfaceName, dhcpResults)) {
- return false;
- }
-
- if (DBG) Log.d(TAG, "DHCP results found for " + mInterfaceName);
- long leaseDuration = dhcpResults.leaseDuration; //int to long conversion
-
- //Sanity check for renewal
- if (leaseDuration >= 0) {
- //TODO: would be good to notify the user that his network configuration is
- //bad and that the device cannot renew below MIN_RENEWAL_TIME_SECS
- if (leaseDuration < MIN_RENEWAL_TIME_SECS) {
- leaseDuration = MIN_RENEWAL_TIME_SECS;
- }
- //Do it a bit earlier than half the lease duration time
- //to beat the native DHCP client and avoid extra packets
- //48% for one hour lease time = 29 minutes
- mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
- SystemClock.elapsedRealtime() +
- leaseDuration * 480, //in milliseconds
- mDhcpRenewalIntent);
- } else {
- //infinite lease time, no renewal needed
- }
-
- // Fill in any missing fields in dhcpResults from the previous results.
- // If mDhcpResults is null (i.e. this is the first server response),
- // this is a noop.
- dhcpResults.updateFromDhcpRequest(mDhcpResults);
- mDhcpResults = dhcpResults;
- mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, dhcpResults)
- .sendToTarget();
- return true;
- }
-
- private boolean runDhcpStart() {
- /* Stop any existing DHCP daemon before starting new */
- NetworkUtils.stopDhcp(mInterfaceName);
- mDhcpResults = null;
-
- if (DBG) Log.d(TAG, "DHCP request on " + mInterfaceName);
- if (!NetworkUtils.startDhcp(mInterfaceName) || !dhcpSucceeded()) {
- Log.e(TAG, "DHCP request failed on " + mInterfaceName + ": " +
- NetworkUtils.getDhcpError());
- mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0)
- .sendToTarget();
- return false;
- }
- return true;
- }
-
- private boolean runDhcpRenew() {
- if (DBG) Log.d(TAG, "DHCP renewal on " + mInterfaceName);
- if (!NetworkUtils.startDhcpRenew(mInterfaceName) || !dhcpSucceeded()) {
- Log.e(TAG, "DHCP renew failed on " + mInterfaceName + ": " +
- NetworkUtils.getDhcpError());
- mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0)
- .sendToTarget();
- return false;
- }
- return true;
- }
-}
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 70f9cc5..1085b1e 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -339,7 +339,7 @@
* <p>
* Writing to this path requires the
* {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permission,
- * and starting in read access requires the
+ * and starting in {@link android.os.Build.VERSION_CODES#KITKAT}, read access requires the
* {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission,
* which is automatically granted if you hold the write permission.
* <p>
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index e7dfbd7..97ee90d 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -865,7 +865,12 @@
}
}
- /** {@hide} */
+ /**
+ * Gets the list of shared/external storage volumes available to the current user.
+ *
+ * <p>It always contains the primary storage volume, plus any additional external volume(s)
+ * available in the device, such as SD cards or attached USB drives.
+ */
public @NonNull StorageVolume[] getVolumeList() {
return getVolumeList(mContext.getUserId(), 0);
}
@@ -914,7 +919,9 @@
return paths;
}
- /** {@hide} */
+ /**
+ * Gets the primary shared/external storage volume available to the current user.
+ */
public @NonNull StorageVolume getPrimaryVolume() {
return getPrimaryVolume(getVolumeList());
}
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 1408202..d860c7d 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -16,11 +16,17 @@
package android.os.storage;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
+import android.content.Intent;
import android.net.TrafficStats;
+import android.net.Uri;
+import android.os.Environment;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
+import android.provider.DocumentsContract;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
@@ -29,14 +35,47 @@
import java.io.File;
/**
- * Information about a storage volume that may be mounted. This is a legacy
- * specialization of {@link VolumeInfo} which describes the volume for a
- * specific user.
- * <p>
- * This class may be deprecated in the future.
+ * Information about a shared/external storage volume for a specific user.
*
- * @hide
+ * <p>
+ * A device always has one (and one only) primary storage volume, but it could have extra volumes,
+ * like SD cards and USB drives. This object represents the logical view of a storage
+ * volume for a specific user: different users might have different views for the same physical
+ * volume (for example, if the volume is a built-in emulated storage).
+ *
+ * <p>
+ * The storage volume is not necessarily mounted, applications should use {@link #getState()} to
+ * verify its state.
+ *
+ * <p>
+ * Applications willing to read or write to this storage volume needs to get a permission from the
+ * user first, which can be achieved in the following ways:
+ *
+ * <ul>
+ * <li>To get access to standard directories (like the {@link Environment#DIRECTORY_PICTURES}), they
+ * can use the {@link #createAccessIntent(String)}. This is the recommend way, since it provides a
+ * simpler API and narrows the access to the given directory (and its descendants).
+ * <li>To get access to any directory (and its descendants), they can use the Storage Acess
+ * Framework APIs (such as {@link Intent#ACTION_OPEN_DOCUMENT} and
+ * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}, although these APIs do not guarantee the user will
+ * select this specific volume.
+ * <li>To get read and write access to the primary storage volume, applications can declare the
+ * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} and
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permissions respectively, with the
+ * latter including the former. This approach is discouraged, since users may be hesitant to grant
+ * broad access to all files contained on a storage device.
+ * </ul>
+ *
+ * <p>It can be obtained through {@link StorageManager#getVolumeList()} and
+ * {@link StorageManager#getPrimaryVolume()} and also as an extra in some broadcasts
+ * (see {@link #EXTRA_STORAGE_VOLUME}).
+ *
+ * <p>
+ * See {@link Environment#getExternalStorageDirectory()} for more info about shared/external
+ * storage semantics.
*/
+// NOTE: This is a legacy specialization of VolumeInfo which describes the volume for a specific
+// user, but is now part of the public API.
public class StorageVolume implements Parcelable {
private final String mId;
@@ -53,14 +92,36 @@
private final String mFsUuid;
private final String mState;
- // StorageVolume extra for ACTION_MEDIA_REMOVED, ACTION_MEDIA_UNMOUNTED, ACTION_MEDIA_CHECKING,
- // ACTION_MEDIA_NOFS, ACTION_MEDIA_MOUNTED, ACTION_MEDIA_SHARED, ACTION_MEDIA_UNSHARED,
- // ACTION_MEDIA_BAD_REMOVAL, ACTION_MEDIA_UNMOUNTABLE and ACTION_MEDIA_EJECT broadcasts.
- public static final String EXTRA_STORAGE_VOLUME = "storage_volume";
+ /**
+ * Name of the {@link Parcelable} extra in the {@link Intent#ACTION_MEDIA_REMOVED},
+ * {@link Intent#ACTION_MEDIA_UNMOUNTED}, {@link Intent#ACTION_MEDIA_CHECKING},
+ * {@link Intent#ACTION_MEDIA_NOFS}, {@link Intent#ACTION_MEDIA_MOUNTED},
+ * {@link Intent#ACTION_MEDIA_SHARED}, {@link Intent#ACTION_MEDIA_BAD_REMOVAL},
+ * {@link Intent#ACTION_MEDIA_UNMOUNTABLE}, and {@link Intent#ACTION_MEDIA_EJECT} broadcast that
+ * contains a {@link StorageVolume}.
+ */
+ // Also sent on ACTION_MEDIA_UNSHARED, which is @hide
+ public static final String EXTRA_STORAGE_VOLUME = "android.os.storage.extra.STORAGE_VOLUME";
+ /**
+ * Name of the String extra used by {@link #createAccessIntent(String) createAccessIntent}.
+ *
+ * @hide
+ */
+ public static final String EXTRA_DIRECTORY_NAME = "android.os.storage.extra.DIRECTORY_NAME";
+
+ /**
+ * Name of the intent used by {@link #createAccessIntent(String) createAccessIntent}.
+ */
+ private static final String ACTION_OPEN_EXTERNAL_DIRECTORY =
+ "android.os.storage.action.OPEN_EXTERNAL_DIRECTORY";
+
+ /** {@hide} */
public static final int STORAGE_ID_INVALID = 0x00000000;
+ /** {@hide} */
public static final int STORAGE_ID_PRIMARY = 0x00010001;
+ /** {@hide} */
public StorageVolume(String id, int storageId, File path, String description, boolean primary,
boolean removable, boolean emulated, long mtpReserveSize, boolean allowMassStorage,
long maxFileSize, UserHandle owner, String fsUuid, String state) {
@@ -95,6 +156,7 @@
mState = in.readString();
}
+ /** {@hide} */
public String getId() {
return mId;
}
@@ -103,17 +165,19 @@
* Returns the mount path for the volume.
*
* @return the mount path
+ * @hide
*/
public String getPath() {
return mPath.toString();
}
+ /** {@hide} */
public File getPathFile() {
return mPath;
}
/**
- * Returns a user visible description of the volume.
+ * Returns a user-visible description of the volume.
*
* @return the volume description
*/
@@ -121,6 +185,10 @@
return mDescription;
}
+ /**
+ * Returns true if the volume is the primary shared/external storage, which is the volume
+ * backed by {@link Environment#getExternalStorageDirectory()}.
+ */
public boolean isPrimary() {
return mPrimary;
}
@@ -148,6 +216,7 @@
* this is also used for the storage_id column in the media provider.
*
* @return MTP storage ID
+ * @hide
*/
public int getStorageId() {
return mStorageId;
@@ -164,6 +233,7 @@
* too close to full.
*
* @return MTP reserve space
+ * @hide
*/
public int getMtpReserveSpace() {
return (int) (mMtpReserveSize / TrafficStats.MB_IN_BYTES);
@@ -173,6 +243,7 @@
* Returns true if this volume can be shared via USB mass storage.
*
* @return whether mass storage is allowed
+ * @hide
*/
public boolean allowMassStorage() {
return mAllowMassStorage;
@@ -182,22 +253,28 @@
* Returns maximum file size for the volume, or zero if it is unbounded.
*
* @return maximum file size
+ * @hide
*/
public long getMaxFileSize() {
return mMaxFileSize;
}
+ /** {@hide} */
public UserHandle getOwner() {
return mOwner;
}
- public String getUuid() {
+ /**
+ * Gets the volume UUID, if any.
+ */
+ public @Nullable String getUuid() {
return mFsUuid;
}
/**
* Parse and return volume UUID as FAT volume ID, or return -1 if unable to
* parse or UUID is unknown.
+ * @hide
*/
public int getFatVolumeId() {
if (mFsUuid == null || mFsUuid.length() != 9) {
@@ -210,14 +287,57 @@
}
}
+ /** {@hide} */
public String getUserLabel() {
return mDescription;
}
+ /**
+ * Returns the current state of the volume.
+ *
+ * @return one of {@link Environment#MEDIA_UNKNOWN}, {@link Environment#MEDIA_REMOVED},
+ * {@link Environment#MEDIA_UNMOUNTED}, {@link Environment#MEDIA_CHECKING},
+ * {@link Environment#MEDIA_NOFS}, {@link Environment#MEDIA_MOUNTED},
+ * {@link Environment#MEDIA_MOUNTED_READ_ONLY}, {@link Environment#MEDIA_SHARED},
+ * {@link Environment#MEDIA_BAD_REMOVAL}, or {@link Environment#MEDIA_UNMOUNTABLE}.
+ */
public String getState() {
return mState;
}
+ /**
+ * Builds an intent to give access to a standard storage directory after obtaining the user's
+ * approval.
+ * <p>
+ * When invoked, the system will ask the user to grant access to the requested directory (and
+ * its descendants). The result of the request will be returned to the activity through the
+ * {@code onActivityResult} method.
+ * <p>
+ * To gain access to descendants (child, grandchild, etc) documents, use
+ * {@link DocumentsContract#buildDocumentUriUsingTree(Uri, String)}, or
+ * {@link DocumentsContract#buildChildDocumentsUriUsingTree(Uri, String)} with the returned URI.
+ *
+ * <b>If your application only needs to store internal data, consider using
+ * {@link Context#getExternalFilesDirs(String) Context.getExternalFilesDirs},
+ * {@link Context#getExternalCacheDirs()}, or
+ * {@link Context#getExternalMediaDirs()}, which require no permissions to read or write.
+ *
+ * @param directoryName must be one of
+ * {@link Environment#DIRECTORY_MUSIC}, {@link Environment#DIRECTORY_PODCASTS},
+ * {@link Environment#DIRECTORY_RINGTONES}, {@link Environment#DIRECTORY_ALARMS},
+ * {@link Environment#DIRECTORY_NOTIFICATIONS}, {@link Environment#DIRECTORY_PICTURES},
+ * {@link Environment#DIRECTORY_MOVIES}, {@link Environment#DIRECTORY_DOWNLOADS},
+ * {@link Environment#DIRECTORY_DCIM}, or {@link Environment#DIRECTORY_DOCUMENTS}
+ *
+ * @see DocumentsContract
+ */
+ public Intent createAccessIntent(@NonNull String directoryName) {
+ final Intent intent = new Intent(ACTION_OPEN_EXTERNAL_DIRECTORY);
+ intent.putExtra(EXTRA_STORAGE_VOLUME, this);
+ intent.putExtra(EXTRA_DIRECTORY_NAME, directoryName);
+ return intent;
+ }
+
@Override
public boolean equals(Object obj) {
if (obj instanceof StorageVolume && mPath != null) {
@@ -234,11 +354,23 @@
@Override
public String toString() {
+ final StringBuilder buffer = new StringBuilder("StorageVolume: ").append(mDescription);
+ if (mFsUuid != null) {
+ buffer.append(" (").append(mFsUuid).append(")");
+ }
+ return buffer.toString();
+ }
+
+ /** {@hide} */
+ // TODO(b/26742218): find out where toString() is called internally and replace these calls by
+ // dump().
+ public String dump() {
final CharArrayWriter writer = new CharArrayWriter();
dump(new IndentingPrintWriter(writer, " ", 80));
return writer.toString();
}
+ /** {@hide} */
public void dump(IndentingPrintWriter pw) {
pw.println("StorageVolume:");
pw.increaseIndent();
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index dfdd36d..904b393 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -4982,6 +4982,17 @@
*/
protected interface PhoneLookupColumns {
/**
+ * The ID of the data row.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String DATA_ID = "data_id";
+ /**
+ * A reference to the {@link ContactsContract.Contacts#_ID} that this
+ * data belongs to.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CONTACT_ID = "contact_id";
+ /**
* The phone number as the user entered it.
* <P>Type: TEXT</P>
*/
@@ -5055,6 +5066,18 @@
* <td>Contact ID.</td>
* </tr>
* <tr>
+ * <td>long</td>
+ * <td>{@link #CONTACT_ID}</td>
+ * <td>read-only</td>
+ * <td>Contact ID.</td>
+ * </tr>
+ * <tr>
+ * <td>long</td>
+ * <td>{@link #DATA_ID}</td>
+ * <td>read-only</td>
+ * <td>Data ID.</td>
+ * </tr>
+ * <tr>
* <td>String</td>
* <td>{@link #LOOKUP_KEY}</td>
* <td>read-only</td>
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index b7f071d..3700098 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -39,6 +39,7 @@
import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.OnCloseListener;
import android.os.RemoteException;
+import android.os.storage.StorageVolume;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;
@@ -62,7 +63,8 @@
* All client apps must hold a valid URI permission grant to access documents,
* typically issued when a user makes a selection through
* {@link Intent#ACTION_OPEN_DOCUMENT}, {@link Intent#ACTION_CREATE_DOCUMENT},
- * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}, or {@link Intent#ACTION_OPEN_EXTERNAL_DIRECTORY}.
+ * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}, or
+ * {@link StorageVolume#createAccessIntent(String) StorageVolume.createAccessIntent}.
*
* @see DocumentsProvider
*/
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 5ab2b00..9632ff7 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1218,6 +1218,8 @@
* Input: Nothing.
* <p>
* Output: Nothing.
+ *
+ * @hide
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_SHOW_ADMIN_SUPPORT_DETAILS
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index e01f2a0..eb3d031 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -72,6 +72,7 @@
public static final int KM_TAG_NO_AUTH_REQUIRED = KM_BOOL | 503;
public static final int KM_TAG_USER_AUTH_TYPE = KM_ENUM | 504;
public static final int KM_TAG_AUTH_TIMEOUT = KM_UINT | 505;
+ public static final int KM_TAG_ALLOW_WHILE_ON_BODY = KM_BOOL | 506;
public static final int KM_TAG_ALL_APPLICATIONS = KM_BOOL | 600;
public static final int KM_TAG_APPLICATION_ID = KM_BYTES | 601;
diff --git a/core/java/android/security/net/config/NetworkSecurityTrustManager.java b/core/java/android/security/net/config/NetworkSecurityTrustManager.java
index 982ed68..81cad79 100644
--- a/core/java/android/security/net/config/NetworkSecurityTrustManager.java
+++ b/core/java/android/security/net/config/NetworkSecurityTrustManager.java
@@ -40,6 +40,9 @@
// TODO: Replace this with a general X509TrustManager and use duck-typing.
private final TrustManagerImpl mDelegate;
private final NetworkSecurityConfig mNetworkSecurityConfig;
+ private final Object mIssuersLock = new Object();
+
+ private X509Certificate[] mIssuers;
public NetworkSecurityTrustManager(NetworkSecurityConfig config) {
if (config == null) {
@@ -139,6 +142,19 @@
@Override
public X509Certificate[] getAcceptedIssuers() {
- return mDelegate.getAcceptedIssuers();
+ // TrustManagerImpl only looks at the provided KeyStore and not the TrustedCertificateStore
+ // for getAcceptedIssuers, so implement it here instead of delegating.
+ synchronized (mIssuersLock) {
+ if (mIssuers == null) {
+ Set<TrustAnchor> anchors = mNetworkSecurityConfig.getTrustAnchors();
+ X509Certificate[] issuers = new X509Certificate[anchors.size()];
+ int i = 0;
+ for (TrustAnchor anchor : anchors) {
+ issuers[i++] = anchor.certificate;
+ }
+ mIssuers = issuers;
+ }
+ return mIssuers.clone();
+ }
}
}
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
index 81c8c45..728f723 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
@@ -72,8 +72,7 @@
* <p>The attribute contains a comma-separated set of signature scheme IDs.
*/
public static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME = "X-Android-APK-Signed";
- // TODO: Change the value when signing scheme finalized.
- public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 1234567890;
+ public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 2;
/**
* Verifies APK Signature Scheme v2 signatures of the provided APK and returns the certificates
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index a4cb703..7017ff5 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -775,10 +775,6 @@
mOwningView.mAttachInfo.mViewRootImpl.registerAnimatingRenderNode(this);
}
- public boolean isAttached() {
- return mOwningView != null && mOwningView.mAttachInfo != null;
- }
-
public void addAnimator(AnimatedVectorDrawable.VectorDrawableAnimator animatorSet) {
if (mOwningView == null || mOwningView.mAttachInfo == null) {
throw new IllegalStateException("Cannot start this animator on a detached view!");
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 9e9ad67..70a0e01 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -21584,7 +21584,7 @@
*/
public void setPointerIcon(PointerIcon pointerIcon) {
mPointerIcon = pointerIcon;
- if (mAttachInfo == null) {
+ if (mAttachInfo == null || mAttachInfo.mHandlingPointerEvent) {
return;
}
try {
@@ -22637,6 +22637,11 @@
boolean mHighContrastText;
/**
+ * Set to true if a pointer event is currently being handled.
+ */
+ boolean mHandlingPointerEvent;
+
+ /**
* Global to the view hierarchy used as a temporary for dealing with
* x/y points in the transparent region computations.
*/
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 96853e0..0517788 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1606,10 +1606,6 @@
frame.height() < desiredWindowHeight && frame.height() != mHeight));
windowShouldResize |= mDragResizing && mResizeMode == RESIZE_MODE_FREEFORM;
- // If the backdrop frame doesn't equal to a frame, we are starting a resize operation, so
- // force it to be resized.
- windowShouldResize |= !mPendingBackDropFrame.equals(mWinFrame);
-
// If the activity was just relaunched, it might have unfrozen the task bounds (while
// relaunching), so we need to force a call into window manager to pick up the latest
// bounds.
@@ -4315,6 +4311,24 @@
private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
+ mAttachInfo.mUnbufferedDispatchRequested = false;
+ final View eventTarget =
+ (event.isFromSource(InputDevice.SOURCE_MOUSE) && mCapturingView != null) ?
+ mCapturingView : mView;
+ mAttachInfo.mHandlingPointerEvent = true;
+ boolean handled = eventTarget.dispatchPointerEvent(event);
+ maybeUpdatePointerIcon(event);
+ mAttachInfo.mHandlingPointerEvent = false;
+ if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
+ mUnbufferedInputDispatch = true;
+ if (mConsumeBatchedInputScheduled) {
+ scheduleConsumeBatchedInputImmediately();
+ }
+ }
+ return handled ? FINISH_HANDLED : FORWARD;
+ }
+
+ private void maybeUpdatePointerIcon(MotionEvent event) {
if (event.getPointerCount() == 1
&& event.isFromSource(InputDevice.SOURCE_MOUSE)) {
if (event.getActionMasked() == MotionEvent.ACTION_HOVER_ENTER
@@ -4331,19 +4345,6 @@
}
}
}
-
- mAttachInfo.mUnbufferedDispatchRequested = false;
- final View eventTarget =
- (event.isFromSource(InputDevice.SOURCE_MOUSE) && mCapturingView != null) ?
- mCapturingView : mView;
- boolean handled = eventTarget.dispatchPointerEvent(event);
- if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
- mUnbufferedInputDispatch = true;
- if (mConsumeBatchedInputScheduled) {
- scheduleConsumeBatchedInputImmediately();
- }
- }
- return handled ? FINISH_HANDLED : FORWARD;
}
private int processTrackballEvent(QueuedInputEvent q) {
diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java
index 1d6e52c..05fd4c8 100644
--- a/core/java/android/widget/TimePickerClockDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -69,10 +69,8 @@
private static final int[] ATTRS_TEXT_COLOR = new int[] {R.attr.textColor};
private static final int[] ATTRS_DISABLED_ALPHA = new int[] {R.attr.disabledAlpha};
- // LayoutLib relies on these constants. Change TimePickerClockDelegate_Delegate if
- // modifying these.
- static final int AM = 0;
- static final int PM = 1;
+ private static final int AM = 0;
+ private static final int PM = 1;
private static final int HOURS_IN_HALF_DAY = 12;
diff --git a/core/java/com/android/internal/app/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl
index 9de4a6c..f4c18c3 100644
--- a/core/java/com/android/internal/app/ISoundTriggerService.aidl
+++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl
@@ -33,10 +33,11 @@
void deleteSoundModel(in ParcelUuid soundModelId);
- void startRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback);
+ int startRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback,
+ in SoundTrigger.RecognitionConfig config);
/**
* Stops recognition.
*/
- void stopRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback);
+ int stopRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback);
}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodUtils.java b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
index 4c63941..62e149a 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodUtils.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
@@ -985,7 +985,7 @@
}
}
- public List<InputMethodInfo> getEnabledInputMethodListLocked() {
+ public ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() {
return createEnabledInputMethodListLocked(
getEnabledInputMethodsAndSubtypeListLocked());
}
@@ -1092,7 +1092,7 @@
return isRemoved;
}
- private List<InputMethodInfo> createEnabledInputMethodListLocked(
+ private ArrayList<InputMethodInfo> createEnabledInputMethodListLocked(
List<Pair<String, ArrayList<String>>> imsList) {
final ArrayList<InputMethodInfo> res = new ArrayList<>();
for (Pair<String, ArrayList<String>> ims: imsList) {
diff --git a/core/java/com/android/internal/util/Protocol.java b/core/java/com/android/internal/util/Protocol.java
index a106f48..5992f7a 100644
--- a/core/java/com/android/internal/util/Protocol.java
+++ b/core/java/com/android/internal/util/Protocol.java
@@ -64,5 +64,6 @@
public static final int BASE_NETWORK_AGENT = 0x00081000;
public static final int BASE_NETWORK_MONITOR = 0x00082000;
public static final int BASE_NETWORK_FACTORY = 0x00083000;
+ public static final int BASE_ETHERNET = 0x00084000;
//TODO: define all used protocols
}
diff --git a/core/jni/android/graphics/Region.cpp b/core/jni/android/graphics/Region.cpp
index bcd0b60..0c30fdc 100644
--- a/core/jni/android/graphics/Region.cpp
+++ b/core/jni/android/graphics/Region.cpp
@@ -212,17 +212,16 @@
android::Parcel* p = android::parcelForJavaObject(env, parcel);
- const size_t size = p->readInt32();
- const void* regionData = p->readInplace(size);
- if (regionData == nullptr) {
+ std::vector<int32_t> rects;
+ p->readInt32Vector(&rects);
+
+ if ((rects.size() % 4) != 0) {
return 0;
}
SkRegion* region = new SkRegion;
- size_t actualSize = region->readFromMemory(regionData, size);
- if (size != actualSize) {
- delete region;
- return 0;
+ for (size_t x = 0; x + 4 <= rects.size(); x += 4) {
+ region->op(rects[x], rects[x+1], rects[x+2], rects[x+3], SkRegion::kUnion_Op);
}
return reinterpret_cast<jlong>(region);
@@ -237,19 +236,18 @@
android::Parcel* p = android::parcelForJavaObject(env, parcel);
- const size_t size = region->writeToMemory(nullptr);
- p->writeInt32(size);
- void* dst = p->writeInplace(size);
- if (dst == nullptr) {
- ALOGE("Region.writeToParcel could not write %zi bytes", size);
- return JNI_FALSE;
- }
- const size_t sizeWritten = region->writeToMemory(dst);
- if (sizeWritten != size) {
- ALOGE("SkRegion::writeToMemory should have written %zi bytes but wrote %zi",
- size, sizeWritten);
+ std::vector<int32_t> rects;
+ SkRegion::Iterator it(*region);
+ while (!it.done()) {
+ const SkIRect& r = it.rect();
+ rects.push_back(r.fLeft);
+ rects.push_back(r.fTop);
+ rects.push_back(r.fRight);
+ rects.push_back(r.fBottom);
+ it.next();
}
+ p->writeInt32Vector(rects);
return JNI_TRUE;
}
diff --git a/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp b/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp
index 14badb7..7a3c598 100644
--- a/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp
+++ b/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp
@@ -43,13 +43,12 @@
return env;
}
-static AnimationListener* createAnimationListener(JNIEnv* env, jobject finishListener, jint id) {
+static AnimationListener* createAnimationListener(JNIEnv* env, jobject finishListener) {
class AnimationListenerBridge : public AnimationListener {
public:
- AnimationListenerBridge(JNIEnv* env, jobject finishListener, jint id) {
+ AnimationListenerBridge(JNIEnv* env, jobject finishListener) {
mFinishListener = env->NewGlobalRef(finishListener);
env->GetJavaVM(&mJvm);
- mId = id;
}
virtual ~AnimationListenerBridge() {
@@ -64,7 +63,7 @@
env->CallStaticVoidMethod(
gVectorDrawableAnimatorClassInfo.clazz,
gVectorDrawableAnimatorClassInfo.callOnFinished,
- mFinishListener, mId);
+ mFinishListener);
releaseJavaObject();
}
@@ -77,9 +76,8 @@
JavaVM* mJvm;
jobject mFinishListener;
- jint mId;
};
- return new AnimationListenerBridge(env, finishListener, id);
+ return new AnimationListenerBridge(env, finishListener);
}
static void addAnimator(JNIEnv*, jobject, jlong animatorSetPtr, jlong propertyHolderPtr,
@@ -144,16 +142,15 @@
holder->setPropertyDataSource(propertyData, length);
env->ReleaseFloatArrayElements(srcData, propertyData, JNI_ABORT);
}
-static void start(JNIEnv* env, jobject, jlong animatorSetPtr, jobject finishListener, jint id) {
+static void start(JNIEnv* env, jobject, jlong animatorSetPtr, jobject finishListener) {
PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorSetPtr);
- AnimationListener* listener = createAnimationListener(env, finishListener, id);
+ // TODO: keep a ref count in finish listener
+ AnimationListener* listener = createAnimationListener(env, finishListener);
set->start(listener);
}
-static void reverse(JNIEnv* env, jobject, jlong animatorSetPtr, jobject finishListener, jint id) {
- PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorSetPtr);
- AnimationListener* listener = createAnimationListener(env, finishListener, id);
- set->reverse(listener);
+static void reverse(JNIEnv* env, jobject, jlong animatorSetPtr, jobject finishListener) {
+ // TODO: implement reverse
}
static void end(JNIEnv*, jobject, jlong animatorSetPtr) {
@@ -175,8 +172,8 @@
{"nCreatePathPropertyHolder", "!(JIFF)J", (void*)createPathPropertyHolder},
{"nCreateRootAlphaPropertyHolder", "!(JFF)J", (void*)createRootAlphaPropertyHolder},
{"nSetPropertyHolderData", "(J[FI)V", (void*)setPropertyHolderData},
- {"nStart", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;I)V", (void*)start},
- {"nReverse", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;I)V", (void*)reverse},
+ {"nStart", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;)V", (void*)start},
+ {"nReverse", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;)V", (void*)reverse},
{"nEnd", "!(J)V", (void*)end},
{"nReset", "!(J)V", (void*)reset},
};
@@ -189,7 +186,7 @@
gVectorDrawableAnimatorClassInfo.callOnFinished = GetStaticMethodIDOrDie(
env, gVectorDrawableAnimatorClassInfo.clazz, "callOnFinished",
- "(Landroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;I)V");
+ "(Landroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;)V");
return RegisterMethodsOrDie(env, "android/graphics/drawable/AnimatedVectorDrawable",
gMethods, NELEM(gMethods));
}
diff --git a/core/jni/android_view_RenderNodeAnimator.cpp b/core/jni/android_view_RenderNodeAnimator.cpp
index c9eac79..0926e9b 100644
--- a/core/jni/android_view_RenderNodeAnimator.cpp
+++ b/core/jni/android_view_RenderNodeAnimator.cpp
@@ -184,7 +184,7 @@
static void end(JNIEnv* env, jobject clazz, jlong animatorPtr) {
BaseRenderNodeAnimator* animator = reinterpret_cast<BaseRenderNodeAnimator*>(animatorPtr);
- animator->cancel();
+ animator->end();
}
// ----------------------------------------------------------------------------
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index ac77007..cd2c0d6 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -17,6 +17,7 @@
#define LOG_TAG "ThreadedRenderer"
#include <algorithm>
+#include <atomic>
#include "jni.h"
#include <nativehelper/JNIHelp.h>
@@ -231,28 +232,13 @@
class NotifyHandler : public MessageHandler {
public:
- NotifyHandler(JavaVM* vm) : mVm(vm) {}
-
- void setObserver(ObserverProxy* observer) {
- mObserver = observer;
- }
-
- void setBuffer(BufferPool::Buffer* buffer) {
- mBuffer = buffer;
- }
-
- void setDropCount(int dropCount) {
- mDropCount = dropCount;
- }
+ NotifyHandler(JavaVM* vm, ObserverProxy* observer) : mVm(vm), mObserver(observer) {}
virtual void handleMessage(const Message& message);
private:
- JavaVM* mVm;
-
- sp<ObserverProxy> mObserver;
- BufferPool::Buffer* mBuffer = nullptr;
- int mDropCount = 0;
+ JavaVM* const mVm;
+ ObserverProxy* const mObserver;
};
static jlongArray get_metrics_buffer(JNIEnv* env, jobject observer) {
@@ -265,6 +251,9 @@
return reinterpret_cast<jlongArray>(buffer);
}
+/*
+ * Implements JNI layer for hwui frame metrics reporting.
+ */
class ObserverProxy : public FrameMetricsObserver {
public:
ObserverProxy(JavaVM *vm, jobject observer) : mVm(vm) {
@@ -284,7 +273,7 @@
mMessageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueLocal);
LOG_ALWAYS_FATAL_IF(mMessageQueue == nullptr, "message queue not available");
- mMessageHandler = new NotifyHandler(mVm);
+ mMessageHandler = new NotifyHandler(mVm, this);
LOG_ALWAYS_FATAL_IF(mMessageHandler == nullptr,
"OOM: unable to allocate NotifyHandler");
}
@@ -298,18 +287,53 @@
return mObserverWeak;
}
- virtual void notify(BufferPool::Buffer* buffer, int dropCount) {
- buffer->incRef();
- mMessageHandler->setBuffer(buffer);
- mMessageHandler->setObserver(this);
- mMessageHandler->setDropCount(dropCount);
- mMessageQueue->getLooper()->sendMessage(mMessageHandler, mMessage);
+ bool getNextBuffer(JNIEnv* env, jlongArray sink, int* dropCount) {
+ FrameMetricsNotification& elem = mRingBuffer[mNextInQueue];
+
+ if (elem.hasData.load()) {
+ env->SetLongArrayRegion(sink, 0, kBufferSize, elem.buffer);
+ *dropCount = elem.dropCount;
+ mNextInQueue = (mNextInQueue + 1) % kRingSize;
+ elem.hasData = false;
+ return true;
+ }
+
+ return false;
+ }
+
+ virtual void notify(const int64_t* stats) {
+ FrameMetricsNotification& elem = mRingBuffer[mNextFree];
+
+ if (!elem.hasData.load()) {
+ memcpy(elem.buffer, stats, kBufferSize * sizeof(stats[0]));
+
+ elem.dropCount = mDroppedReports;
+ mDroppedReports = 0;
+
+ incStrong(nullptr);
+ mNextFree = (mNextFree + 1) % kRingSize;
+ elem.hasData = true;
+
+ mMessageQueue->getLooper()->sendMessage(mMessageHandler, mMessage);
+ } else {
+ mDroppedReports++;
+ }
}
private:
static const int kBufferSize = static_cast<int>(FrameInfoIndex::NumIndexes);
+ static constexpr int kRingSize = 3;
- JavaVM* mVm;
+ class FrameMetricsNotification {
+ public:
+ FrameMetricsNotification() : hasData(false) {}
+
+ std::atomic_bool hasData;
+ int64_t buffer[kBufferSize];
+ int dropCount = 0;
+ };
+
+ JavaVM* const mVm;
jweak mObserverWeak;
jobject mJavaBufferGlobal;
@@ -317,28 +341,28 @@
sp<NotifyHandler> mMessageHandler;
Message mMessage;
+ int mNextFree = 0;
+ int mNextInQueue = 0;
+ FrameMetricsNotification mRingBuffer[kRingSize];
+
+ int mDroppedReports = 0;
};
void NotifyHandler::handleMessage(const Message& message) {
JNIEnv* env = getenv(mVm);
- ObserverProxy* observer = mObserver.get();
- LOG_ALWAYS_FATAL_IF(observer == nullptr, "received message with no observer configured");
- LOG_ALWAYS_FATAL_IF(mBuffer == nullptr, "received message with no data to report");
-
- jobject target = env->NewLocalRef(observer->getObserverReference());
+ jobject target = env->NewLocalRef(mObserver->getObserverReference());
if (target != nullptr) {
jlongArray javaBuffer = get_metrics_buffer(env, target);
- env->SetLongArrayRegion(javaBuffer,
- 0, mBuffer->getSize(), mBuffer->getBuffer());
- env->CallVoidMethod(target, gFrameMetricsObserverClassInfo.callback,
- mDropCount);
+ int dropCount = 0;
+ while (mObserver->getNextBuffer(env, javaBuffer, &dropCount)) {
+ env->CallVoidMethod(target, gFrameMetricsObserverClassInfo.callback, dropCount);
+ }
env->DeleteLocalRef(target);
}
- mBuffer->release();
- mObserver.clear();
+ mObserver->decStrong(nullptr);
}
static void android_view_ThreadedRenderer_setAtlas(JNIEnv* env, jobject clazz,
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7699da7..9b989b9 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -237,12 +237,21 @@
<protected-broadcast android:name="android.net.nsd.STATE_CHANGED" />
- <protected-broadcast android:name="android.nfc.action.LLCP_LINK_STATE_CHANGED" />
+ <protected-broadcast android:name="android.nfc.action.ADAPTER_STATE_CHANGED" />
+ <protected-broadcast android:name="android.nfc.action.TRANSACTION_DETECTED" />
+ <protected-broadcast android:name="com.android.nfc.action.LLCP_UP" />
+ <protected-broadcast android:name="com.android.nfc.action.LLCP_DOWN" />
+ <protected-broadcast android:name="com.android.nfc.cardemulation.action.CLOSE_TAP_DIALOG" />
+ <protected-broadcast android:name="com.android.nfc.handover.action.ALLOW_CONNECT" />
+ <protected-broadcast android:name="com.android.nfc.handover.action.DENY_CONNECT" />
<protected-broadcast android:name="com.android.nfc_extras.action.RF_FIELD_ON_DETECTED" />
<protected-broadcast android:name="com.android.nfc_extras.action.RF_FIELD_OFF_DETECTED" />
<protected-broadcast android:name="com.android.nfc_extras.action.AID_SELECTED" />
-
- <protected-broadcast android:name="android.nfc.action.TRANSACTION_DETECTED" />
+ <!-- For NFC to BT handover -->
+ <protected-broadcast android:name="android.btopp.intent.action.WHITELIST_DEVICE" />
+ <protected-broadcast android:name="android.btopp.intent.action.STOP_HANDOVER_TRANSFER" />
+ <protected-broadcast android:name="android.nfc.handover.intent.action.HANDOVER_SEND" />
+ <protected-broadcast android:name="android.nfc.handover.intent.action.HANDOVER_SEND_MULTIPLE" />
<protected-broadcast android:name="android.intent.action.CLEAR_DNS_CACHE" />
<protected-broadcast android:name="android.intent.action.PROXY_CHANGE" />
diff --git a/core/res/res/layout/app_error_dialog.xml b/core/res/res/layout/app_error_dialog.xml
index 824f97f..b9531f4 100644
--- a/core/res/res/layout/app_error_dialog.xml
+++ b/core/res/res/layout/app_error_dialog.xml
@@ -66,7 +66,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/aerr_mute"
- android:drawableStart="@drawable/ic_close"
+ android:drawableStart="@drawable/ic_eject_24dp"
style="@style/aerr_list_item"
/>
diff --git a/core/res/res/layout/notification_material_action.xml b/core/res/res/layout/notification_material_action.xml
index 398f52d..548ee05 100644
--- a/core/res/res/layout/notification_material_action.xml
+++ b/core/res/res/layout/notification_material_action.xml
@@ -21,6 +21,7 @@
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_gravity="center"
+ android:gravity="start|center_vertical"
android:layout_marginStart="4dp"
android:textColor="@color/notification_default_color"
android:singleLine="true"
diff --git a/core/res/res/layout/notification_material_action_tombstone.xml b/core/res/res/layout/notification_material_action_tombstone.xml
index 976448b..1f59ea0 100644
--- a/core/res/res/layout/notification_material_action_tombstone.xml
+++ b/core/res/res/layout/notification_material_action_tombstone.xml
@@ -18,16 +18,15 @@
<Button xmlns:android="http://schemas.android.com/apk/res/android"
style="@android:style/Widget.Material.Light.Button.Borderless.Small"
android:id="@+id/action0"
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="48dp"
- android:layout_weight="1"
+ android:layout_marginStart="4dp"
+ android:layout_gravity="center"
android:gravity="start|center_vertical"
- android:drawablePadding="8dp"
- android:paddingStart="8dp"
android:textColor="#555555"
- android:textSize="@dimen/notification_text_size"
android:singleLine="true"
android:ellipsize="end"
android:alpha="0.5"
android:enabled="false"
+ android:background="@drawable/notification_material_action_background"
/>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index be8577a..4480944 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5030,7 +5030,7 @@
<attr name="firstDayOfWeek" format="integer" />
<!-- The minimal date shown by this calendar view in mm/dd/yyyy format. -->
<attr name="minDate" />
- <!-- The minimal date shown by this calendar view in mm/dd/yyyy format. -->
+ <!-- The maximal date shown by this calendar view in mm/dd/yyyy format. -->
<attr name="maxDate" />
<!-- The text appearance for the month and year in the calendar header. -->
<attr name="monthTextAppearance" format="reference" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 078aa41..1f0e96d 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2608,8 +2608,8 @@
<string name="aerr_report">Send feedback</string>
<!-- Button that closes a crashed application -->
<string name="aerr_close">Close</string>
- <!-- Button that mutes further crashes of the crashed application-->
- <string name="aerr_mute">Mute</string>
+ <!-- Button that mutes further crashes of the crashed application. Note that this only appears on engineering builds. -->
+ <string name="aerr_mute">Mute until device restarts</string>
<!-- Button that waits a bit more for an unresponsive app -->
<string name="aerr_wait">Wait</string>
<!-- Button that closes an unresponsive application -->
@@ -2922,9 +2922,9 @@
<!-- Title of notification shown to ask for user consent for sharing a bugreport that was requested remotely by the IT administrator. -->
<string name="share_remote_bugreport_notification_title">Share bug report?</string>
<!-- Title of notification shown to indicate that bug report is still being collected after sharing was accepted. -->
- <string name="sharing_remote_bugreport_notification_title">Sharing bug report</string>
+ <string name="sharing_remote_bugreport_notification_title">Sharing bug report\u2026</string>
<!-- Message of notification shown to ask for user consent for sharing a bugreport that was requested remotely by the IT administrator. -->
- <string name="share_remote_bugreport_notification_message">Your IT admin requested a bug report to help troubleshoot this device. Apps and data may be shared. This may temporarily slow down your device.</string>
+ <string name="share_remote_bugreport_notification_message">Your IT admin requested a bug report to help troubleshoot this device. Apps and data may be shared and your device may temporarily slow down.</string>
<!-- Message of notification shown to ask for user consent for sharing a bugreport that was requested remotely by the IT administrator. -->
<string name="share_finished_remote_bugreport_notification_message">Your IT admin requested a bug report to help troubleshoot this device. Apps and data may be shared.</string>
<!-- Message of notification shown to shown to indicate that bug report is still being collected after sharing was accepted. -->
diff --git a/core/tests/coretests/src/android/app/backup/FullBackupTest.java b/core/tests/coretests/src/android/app/backup/FullBackupTest.java
index c3afbf6..3869cd2 100644
--- a/core/tests/coretests/src/android/app/backup/FullBackupTest.java
+++ b/core/tests/coretests/src/android/app/backup/FullBackupTest.java
@@ -66,7 +66,7 @@
assertEquals("Excluding files when there was no <exclude/> tag.", 0, excludesSet.size());
assertEquals("Unexpected number of <include/>s", 1, includeMap.size());
- Set<String> fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN);
+ Set<String> fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN);
assertEquals("Didn't find expected file domain include.", 1, fileDomainIncludes.size());
assertEquals("Invalid path parsed for <include/>",
new File(mContext.getFilesDir(), "onlyInclude.txt").getCanonicalPath(),
@@ -99,7 +99,7 @@
FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
- Set<String> fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN);
+ Set<String> fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN);
assertEquals("Didn't find expected file domain include.", 1, fileDomainIncludes.size());
assertEquals("Invalid path parsed for <include/>",
new File(mContext.getFilesDir(), "include.txt").getCanonicalPath(),
@@ -128,15 +128,16 @@
FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
- Set<String> fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN);
+ Set<String> fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN);
assertEquals("Didn't find expected file domain include.", 1, fileDomainIncludes.size());
assertEquals("Invalid path parsed for <include/>",
new File(mContext.getFilesDir(), "include1.txt").getCanonicalPath(),
fileDomainIncludes.iterator().next());
Set<String> databaseDomainIncludes = includeMap.get(FullBackup.DATABASE_TREE_TOKEN);
+ // Three expected here because of "-journal" and "-wal" files
assertEquals("Didn't find expected database domain include.",
- 2, databaseDomainIncludes.size()); // two expected here because of "-journal" file
+ 3, databaseDomainIncludes.size());
assertTrue("Invalid path parsed for <include/>",
databaseDomainIncludes.contains(
new File(mContext.getDatabasePath("foo").getParentFile(), "include2.txt")
@@ -147,6 +148,12 @@
mContext.getDatabasePath("foo").getParentFile(),
"include2.txt-journal")
.getCanonicalPath()));
+ assertTrue("Invalid path parsed for <include/>",
+ databaseDomainIncludes.contains(
+ new File(
+ mContext.getDatabasePath("foo").getParentFile(),
+ "include2.txt-wal")
+ .getCanonicalPath()));
List<String> sharedPrefDomainIncludes = new ArrayList<String>(
includeMap.get(FullBackup.SHAREDPREFS_TREE_TOKEN));
@@ -168,7 +175,7 @@
sharedPrefDomainIncludes.get(2));
- assertEquals("Unexpected number of <exclude/>s", 6, excludesSet.size());
+ assertEquals("Unexpected number of <exclude/>s", 7, excludesSet.size());
// Sets are annoying to iterate over b/c order isn't enforced - convert to an array and
// sort lexicographically.
List<String> arrayedSet = new ArrayList<String>(excludesSet);
@@ -183,20 +190,24 @@
.getCanonicalPath(),
arrayedSet.get(1));
assertEquals("Invalid path parsed for <exclude/>",
- new File(mContext.getFilesDir(), "exclude1.txt").getCanonicalPath(),
+ new File(mContext.getDatabasePath("foo").getParentFile(), "exclude2.txt-wal")
+ .getCanonicalPath(),
arrayedSet.get(2));
assertEquals("Invalid path parsed for <exclude/>",
+ new File(mContext.getFilesDir(), "exclude1.txt").getCanonicalPath(),
+ arrayedSet.get(3));
+ assertEquals("Invalid path parsed for <exclude/>",
new File(mContext.getSharedPrefsFile("foo").getParentFile(), "exclude3")
.getCanonicalPath(),
- arrayedSet.get(3));
+ arrayedSet.get(4));
assertEquals("Invalid path parsed for <exclude/>",
new File(mContext.getSharedPrefsFile("foo").getParentFile(), "exclude3.xml")
.getCanonicalPath(),
- arrayedSet.get(4));
+ arrayedSet.get(5));
assertEquals("Invalid path parsed for <exclude/>",
new File(mContext.getSharedPrefsFile("foo").getParentFile(), "exclude4.xml")
.getCanonicalPath(),
- arrayedSet.get(5));
+ arrayedSet.get(6));
}
public void testParseBackupSchemeFromXml_invalidXmlFails() throws Exception {
@@ -247,7 +258,7 @@
assertEquals("Didn't throw away invalid \"..\" path.", 0, includeMap.size());
- Set<String> fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN);
+ Set<String> fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN);
assertNull("Didn't throw away invalid \"..\" path.", fileDomainIncludes);
}
public void testDoubleDotInPath_isIgnored() throws Exception {
@@ -261,7 +272,7 @@
assertEquals("Didn't throw away invalid \"..\" path.", 0, includeMap.size());
- Set<String> fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN);
+ Set<String> fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN);
assertNull("Didn't throw away invalid \"..\" path.", fileDomainIncludes);
}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk
index edeecb2..26f768b 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk
@@ -29,6 +29,8 @@
LOCAL_DEX_PREOPT := false
+LOCAL_JAVACFLAGS := -nowarn
+
mainDexList:= \
$(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/Test.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/Test.java
index 5e931bc..8d065a4 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/Test.java
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/Test.java
@@ -15,14 +15,13 @@
*/
package com.android.multidexlegacyandexception;
-import android.test.ActivityInstrumentationTestCase2;
-
/**
* Run the tests with: <code>adb shell am instrument -w
com.android.multidexlegacyandexception/android.test.InstrumentationTestRunner
</code>
*/
-public class Test extends ActivityInstrumentationTestCase2<MainActivity> {
+@SuppressWarnings("deprecation")
+public class Test extends android.test.ActivityInstrumentationTestCase2<MainActivity> {
public Test() {
super(MainActivity.class);
}
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
index 77748a8..af8ccf5 100644
--- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -238,6 +238,9 @@
mAnimatorSet.recordLastSeenTarget((DisplayListCanvas) canvas);
}
mAnimatedVectorState.mVectorDrawable.draw(canvas);
+ if (isStarted()) {
+ invalidateSelf();
+ }
}
@Override
@@ -608,6 +611,10 @@
return mAnimatorSet.isRunning();
}
+ private boolean isStarted() {
+ return mAnimatorSet.isStarted();
+ }
+
/**
* Resets the AnimatedVectorDrawable to the start state as specified in the animators.
*/
@@ -619,6 +626,12 @@
@Override
public void start() {
ensureAnimatorSet();
+
+ // If any one of the animator has not ended, do nothing.
+ if (isStarted()) {
+ return;
+ }
+
mAnimatorSet.start();
invalidateSelf();
}
@@ -639,7 +652,6 @@
@Override
public void stop() {
mAnimatorSet.end();
- invalidateSelf();
}
/**
@@ -762,9 +774,6 @@
* @hide
*/
public static class VectorDrawableAnimator {
- private static final int NONE = 0;
- private static final int START_ANIMATION = 1;
- private static final int REVERSE_ANIMATION = 2;
private AnimatorListener mListener = null;
private final LongArray mStartDelays = new LongArray();
private PropertyValuesHolder.PropertyValues mTmpValues =
@@ -773,14 +782,15 @@
private boolean mContainsSequentialAnimators = false;
private boolean mStarted = false;
private boolean mInitialized = false;
+ private boolean mAnimationPending = false;
private boolean mIsReversible = false;
// This needs to be set before parsing starts.
private boolean mShouldIgnoreInvalidAnim;
// TODO: Consider using NativeAllocationRegistery to track native allocation
private final VirtualRefBasePtr mSetRefBasePtr;
+ private WeakReference<RenderNode> mTarget = null;
private WeakReference<RenderNode> mLastSeenTarget = null;
- private int mLastListenerId = 0;
- private int mPendingAnimationAction = NONE;
+
VectorDrawableAnimator() {
mSetPtr = nCreateAnimatorSet();
@@ -800,7 +810,6 @@
mInitialized = true;
// Check reversible.
- mIsReversible = true;
if (mContainsSequentialAnimators) {
mIsReversible = false;
} else {
@@ -812,6 +821,7 @@
}
}
}
+ mIsReversible = true;
}
private void parseAnimatorSet(AnimatorSet set, long startTime) {
@@ -1032,28 +1042,36 @@
* to the last seen RenderNode target and start right away.
*/
protected void recordLastSeenTarget(DisplayListCanvas canvas) {
- mLastSeenTarget = new WeakReference<RenderNode>(
- RenderNodeAnimatorSetHelper.getTarget(canvas));
- if (mPendingAnimationAction != NONE) {
+ if (mAnimationPending) {
+ mLastSeenTarget = new WeakReference<RenderNode>(
+ RenderNodeAnimatorSetHelper.getTarget(canvas));
if (DBG_ANIMATION_VECTOR_DRAWABLE) {
Log.d(LOGTAG, "Target is set in the next frame");
}
- if (mPendingAnimationAction == START_ANIMATION) {
- start();
- } else if (mPendingAnimationAction == REVERSE_ANIMATION) {
- reverse();
- }
- mPendingAnimationAction = NONE;
+ mAnimationPending = false;
+ start();
+ } else {
+ mLastSeenTarget = new WeakReference<RenderNode>(
+ RenderNodeAnimatorSetHelper.getTarget(canvas));
}
+
+ }
+
+ private boolean setTarget(RenderNode node) {
+ if (mTarget != null && mTarget.get() != null) {
+ // TODO: Maybe we want to support target change.
+ throw new IllegalStateException("Target already set!");
+ }
+
+ node.addAnimator(this);
+ mTarget = new WeakReference<RenderNode>(node);
+ return true;
}
private boolean useLastSeenTarget() {
- if (mLastSeenTarget != null) {
- final RenderNode target = mLastSeenTarget.get();
- if (target != null && target.isAttached()) {
- target.addAnimator(this);
- return true;
- }
+ if (mLastSeenTarget != null && mLastSeenTarget.get() != null) {
+ setTarget(mLastSeenTarget.get());
+ return true;
}
return false;
}
@@ -1063,8 +1081,12 @@
return;
}
+ if (mStarted) {
+ return;
+ }
+
if (!useLastSeenTarget()) {
- mPendingAnimationAction = START_ANIMATION;
+ mAnimationPending = true;
return;
}
@@ -1072,45 +1094,38 @@
Log.d(LOGTAG, "Target is set. Starting VDAnimatorSet from java");
}
- mStarted = true;
- nStart(mSetPtr, this, ++mLastListenerId);
+ nStart(mSetPtr, this);
if (mListener != null) {
mListener.onAnimationStart(null);
}
+ mStarted = true;
}
public void end() {
- if (mInitialized && useLastSeenTarget()) {
- // If no target has ever been set, no-op
+ if (mInitialized && mStarted) {
nEnd(mSetPtr);
+ onAnimationEnd();
}
}
- public void reset() {
- if (mInitialized && useLastSeenTarget()) {
- // If no target has ever been set, no-op
- nReset(mSetPtr);
+ void reset() {
+ if (!mInitialized) {
+ return;
}
+ // TODO: Need to implement reset.
+ Log.w(LOGTAG, "Reset is yet to be implemented");
+ nReset(mSetPtr);
}
// Current (imperfect) Java AnimatorSet cannot be reversed when the set contains sequential
// animators or when the animator set has a start delay
void reverse() {
- if (!mIsReversible || !mInitialized) {
+ if (!mIsReversible) {
return;
}
- if (!useLastSeenTarget()) {
- mPendingAnimationAction = REVERSE_ANIMATION;
- return;
- }
- if (DBG_ANIMATION_VECTOR_DRAWABLE) {
- Log.d(LOGTAG, "Target is set. Reversing VDAnimatorSet from java");
- }
- mStarted = true;
- nReverse(mSetPtr, this, ++mLastListenerId);
- if (mListener != null) {
- mListener.onAnimationStart(null);
- }
+ // TODO: Need to support reverse (non-public API)
+ Log.w(LOGTAG, "Reverse is yet to be implemented");
+ nReverse(mSetPtr, this);
}
public long getAnimatorNativePtr() {
@@ -1140,22 +1155,20 @@
mListener = null;
}
- private void onAnimationEnd(int listenerId) {
- if (listenerId != mLastListenerId) {
- return;
- }
- if (DBG_ANIMATION_VECTOR_DRAWABLE) {
- Log.d(LOGTAG, "on finished called from native");
- }
+ private void onAnimationEnd() {
mStarted = false;
if (mListener != null) {
mListener.onAnimationEnd(null);
}
+ mTarget = null;
}
// onFinished: should be called from native
- private static void callOnFinished(VectorDrawableAnimator set, int id) {
- set.onAnimationEnd(id);
+ private static void callOnFinished(VectorDrawableAnimator set) {
+ if (DBG_ANIMATION_VECTOR_DRAWABLE) {
+ Log.d(LOGTAG, "on finished called from native");
+ }
+ set.onAnimationEnd();
}
}
@@ -1175,8 +1188,8 @@
private static native long nCreateRootAlphaPropertyHolder(long nativePtr, float startValue,
float endValue);
private static native void nSetPropertyHolderData(long nativePtr, float[] data, int length);
- private static native void nStart(long animatorSetPtr, VectorDrawableAnimator set, int id);
- private static native void nReverse(long animatorSetPtr, VectorDrawableAnimator set, int id);
+ private static native void nStart(long animatorSetPtr, VectorDrawableAnimator set);
+ private static native void nReverse(long animatorSetPtr, VectorDrawableAnimator set);
private static native void nEnd(long animatorSetPtr);
private static native void nReset(long animatorSetPtr);
}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
index e6276a4..1321a83 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
@@ -233,7 +233,8 @@
// not set up).
KeymasterUtils.addUserAuthArgs(new KeymasterArguments(),
spec.isUserAuthenticationRequired(),
- spec.getUserAuthenticationValidityDurationSeconds());
+ spec.getUserAuthenticationValidityDurationSeconds(),
+ spec.isUserAuthenticationValidWhileOnBody());
} catch (IllegalStateException | IllegalArgumentException e) {
throw new InvalidAlgorithmParameterException(e);
}
@@ -271,7 +272,8 @@
args.addEnums(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigests);
KeymasterUtils.addUserAuthArgs(args,
spec.isUserAuthenticationRequired(),
- spec.getUserAuthenticationValidityDurationSeconds());
+ spec.getUserAuthenticationValidityDurationSeconds(),
+ spec.isUserAuthenticationValidWhileOnBody());
KeymasterUtils.addMinMacLengthAuthorizationIfNecessary(
args,
mKeymasterAlgorithm,
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
index 3a0ff1c..830402a 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -344,7 +344,8 @@
// not set up).
KeymasterUtils.addUserAuthArgs(new KeymasterArguments(),
mSpec.isUserAuthenticationRequired(),
- mSpec.getUserAuthenticationValidityDurationSeconds());
+ mSpec.getUserAuthenticationValidityDurationSeconds(),
+ mSpec.isUserAuthenticationValidWhileOnBody());
} catch (IllegalArgumentException | IllegalStateException e) {
throw new InvalidAlgorithmParameterException(e);
}
@@ -529,7 +530,8 @@
KeymasterUtils.addUserAuthArgs(args,
mSpec.isUserAuthenticationRequired(),
- mSpec.getUserAuthenticationValidityDurationSeconds());
+ mSpec.getUserAuthenticationValidityDurationSeconds(),
+ mSpec.isUserAuthenticationValidWhileOnBody());
args.addDateIfNotNull(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, mSpec.getKeyValidityStart());
args.addDateIfNotNull(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
mSpec.getKeyValidityForOriginationEnd());
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
index 8d606bf..5f5f2c2 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
@@ -167,6 +167,8 @@
boolean userAuthenticationRequirementEnforcedBySecureHardware = (userAuthenticationRequired)
&& (keymasterHwEnforcedUserAuthenticators != 0)
&& (keymasterSwEnforcedUserAuthenticators == 0);
+ boolean userAuthenticationValidWhileOnBody =
+ keyCharacteristics.hwEnforced.getBoolean(KeymasterDefs.KM_TAG_ALLOW_WHILE_ON_BODY);
return new KeyInfo(entryAlias,
insideSecureHardware,
@@ -182,7 +184,8 @@
blockModes,
userAuthenticationRequired,
(int) userAuthenticationValidityDurationSeconds,
- userAuthenticationRequirementEnforcedBySecureHardware);
+ userAuthenticationRequirementEnforcedBySecureHardware,
+ userAuthenticationValidWhileOnBody);
}
@Override
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java
index cdcc7a2..d660020 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java
@@ -498,7 +498,8 @@
KeyProperties.SignaturePadding.allToKeymaster(spec.getSignaturePaddings()));
KeymasterUtils.addUserAuthArgs(importArgs,
spec.isUserAuthenticationRequired(),
- spec.getUserAuthenticationValidityDurationSeconds());
+ spec.getUserAuthenticationValidityDurationSeconds(),
+ spec.isUserAuthenticationValidWhileOnBody());
importArgs.addDateIfNotNull(KeymasterDefs.KM_TAG_ACTIVE_DATETIME,
spec.getKeyValidityStart());
importArgs.addDateIfNotNull(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
@@ -692,7 +693,8 @@
args.addEnums(KeymasterDefs.KM_TAG_PADDING, keymasterPaddings);
KeymasterUtils.addUserAuthArgs(args,
params.isUserAuthenticationRequired(),
- params.getUserAuthenticationValidityDurationSeconds());
+ params.getUserAuthenticationValidityDurationSeconds(),
+ params.isUserAuthenticationValidWhileOnBody());
KeymasterUtils.addMinMacLengthAuthorizationIfNecessary(
args,
keymasterAlgorithm,
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index f3fd129..a84e7f34 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -252,6 +252,7 @@
private final int mUserAuthenticationValidityDurationSeconds;
private final byte[] mAttestationChallenge;
private final boolean mUniqueIdIncluded;
+ private final boolean mUserAuthenticationValidWhileOnBody;
/**
* @hide should be built with Builder
@@ -277,7 +278,8 @@
boolean userAuthenticationRequired,
int userAuthenticationValidityDurationSeconds,
byte[] attestationChallenge,
- boolean uniqueIdIncluded) {
+ boolean uniqueIdIncluded,
+ boolean userAuthenticationValidWhileOnBody) {
if (TextUtils.isEmpty(keyStoreAlias)) {
throw new IllegalArgumentException("keyStoreAlias must not be empty");
}
@@ -321,6 +323,7 @@
mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
mAttestationChallenge = Utils.cloneIfNotNull(attestationChallenge);
mUniqueIdIncluded = uniqueIdIncluded;
+ mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody;
}
/**
@@ -587,6 +590,23 @@
}
/**
+ * Returns {@code true} if the key will remain authorized while the device is on the user's
+ * body, even after the validity duration has expired. This option has no effect on keys that
+ * don't have an authentication validity duration, and has no effect if the device lacks a
+ * secure on-body sensor.
+ *
+ * <p>Authorization applies only to secret key and private key operations. Public key operations
+ * are not restricted.
+ *
+ * @see #isUserAuthenticationRequired()
+ * @see #getUserAuthenticationValidityDurationSeconds()
+ * @see Builder#setUserAuthenticationValidWhileOnBody(boolean)
+ */
+ public boolean isUserAuthenticationValidWhileOnBody() {
+ return mUserAuthenticationValidWhileOnBody;
+ }
+
+ /**
* Builder of {@link KeyGenParameterSpec} instances.
*/
public final static class Builder {
@@ -612,6 +632,7 @@
private int mUserAuthenticationValidityDurationSeconds = -1;
private byte[] mAttestationChallenge = null;
private boolean mUniqueIdIncluded = false;
+ private boolean mUserAuthenticationValidWhileOnBody;
/**
* Creates a new instance of the {@code Builder}.
@@ -1061,6 +1082,34 @@
}
/**
+ * Sets whether the key is authorized for use after the authentication validity period is
+ * expired (see {@link #setUserAuthenticationValidityDurationSeconds} and {@link
+ * #setUserAuthenticationRequired}) if the device has a secure on-body sensor and if the
+ * device has not been removed from the user's body since the last successful
+ * authentication.
+ *
+ * <p>On devices that do not have a secure on-body sensor, creating a key with this
+ * parameter set to {@code true} will have no effect; the private or secret key will no
+ * longer be authorized for use after the validity period ends, and a fresh authentication
+ * will be required to use it again.
+ *
+ * <p>Note that "secure" on-body sensors are required by Android to have a secure path to
+ * the secure hardware, but the sensors themselves may not be difficult to fool. It is
+ * recommended that this feature be used to increase slightly the security of keys which
+ * would otherwise have to allow unauthenticated access, or have a very long validity
+ * period. Keys that require high assurance of user authorization should not use this
+ * feature and should set a short validity period.
+ *
+ * @param remainsValid if {@code true}, and if the device supports secure on-body detection,
+ * key will remain valid after authentication validity duration has expired.
+ */
+ @NonNull
+ public Builder setUserAuthenticationValidWhileOnBody(boolean remainsValid) {
+ mUserAuthenticationValidWhileOnBody = remainsValid;
+ return this;
+ }
+
+ /**
* Builds an instance of {@code KeyGenParameterSpec}.
*/
@NonNull
@@ -1086,7 +1135,8 @@
mUserAuthenticationRequired,
mUserAuthenticationValidityDurationSeconds,
mAttestationChallenge,
- mUniqueIdIncluded);
+ mUniqueIdIncluded,
+ mUserAuthenticationValidWhileOnBody);
}
}
}
diff --git a/keystore/java/android/security/keystore/KeyInfo.java b/keystore/java/android/security/keystore/KeyInfo.java
index d726880..f77b5ba 100644
--- a/keystore/java/android/security/keystore/KeyInfo.java
+++ b/keystore/java/android/security/keystore/KeyInfo.java
@@ -79,6 +79,7 @@
private final boolean mUserAuthenticationRequired;
private final int mUserAuthenticationValidityDurationSeconds;
private final boolean mUserAuthenticationRequirementEnforcedBySecureHardware;
+ private final boolean mUserAuthenticationValidWhileOnBody;
/**
* @hide
@@ -97,7 +98,8 @@
@KeyProperties.BlockModeEnum String[] blockModes,
boolean userAuthenticationRequired,
int userAuthenticationValidityDurationSeconds,
- boolean userAuthenticationRequirementEnforcedBySecureHardware) {
+ boolean userAuthenticationRequirementEnforcedBySecureHardware,
+ boolean userAuthenticationValidWhileOnBody) {
mKeystoreAlias = keystoreKeyAlias;
mInsideSecureHardware = insideSecureHardware;
mOrigin = origin;
@@ -116,6 +118,7 @@
mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
mUserAuthenticationRequirementEnforcedBySecureHardware =
userAuthenticationRequirementEnforcedBySecureHardware;
+ mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody;
}
/**
@@ -277,4 +280,14 @@
public boolean isUserAuthenticationRequirementEnforcedBySecureHardware() {
return mUserAuthenticationRequirementEnforcedBySecureHardware;
}
+
+ /**
+ * Returns {@code true} if this key will remain usable after its specified validity duration
+ * for as long as the device remains on the user's body. This is possible only for keys with
+ * a specified validity duration. Always returns {@code false} on devices that lack a secure
+ * on-body sensor.
+ */
+ public boolean isUserAuthenticationValidWhileOnBody() {
+ return mUserAuthenticationValidWhileOnBody;
+ }
}
diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java
index c984439..4700b68 100644
--- a/keystore/java/android/security/keystore/KeyProtection.java
+++ b/keystore/java/android/security/keystore/KeyProtection.java
@@ -214,6 +214,7 @@
private final boolean mRandomizedEncryptionRequired;
private final boolean mUserAuthenticationRequired;
private final int mUserAuthenticationValidityDurationSeconds;
+ private final boolean mUserAuthenticationValidWhileOnBody;
private KeyProtection(
Date keyValidityStart,
@@ -226,7 +227,8 @@
@KeyProperties.BlockModeEnum String[] blockModes,
boolean randomizedEncryptionRequired,
boolean userAuthenticationRequired,
- int userAuthenticationValidityDurationSeconds) {
+ int userAuthenticationValidityDurationSeconds,
+ boolean userAuthenticationValidWhileOnBody) {
mKeyValidityStart = Utils.cloneIfNotNull(keyValidityStart);
mKeyValidityForOriginationEnd = Utils.cloneIfNotNull(keyValidityForOriginationEnd);
mKeyValidityForConsumptionEnd = Utils.cloneIfNotNull(keyValidityForConsumptionEnd);
@@ -240,6 +242,7 @@
mRandomizedEncryptionRequired = randomizedEncryptionRequired;
mUserAuthenticationRequired = userAuthenticationRequired;
mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
+ mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody;
}
/**
@@ -392,6 +395,23 @@
}
/**
+ * Returns {@code true} if the key will remain authorized while the device is on the user's
+ * body, even after the validity duration has expired. This option has no effect on keys that
+ * don't have an authentication validity duration, and has no effect if the device lacks a
+ * secure on-body sensor.
+ *
+ * <p>Authorization applies only to secret key and private key operations. Public key operations
+ * are not restricted.
+ *
+ * @see #isUserAuthenticationRequired()
+ * @see #getUserAuthenticationValidityDurationSeconds()
+ * @see Builder#setUserAuthenticationValidWhileOnBody(boolean)
+ */
+ public boolean isUserAuthenticationValidWhileOnBody() {
+ return mUserAuthenticationValidWhileOnBody;
+ }
+
+ /**
* Builder of {@link KeyProtection} instances.
*/
public final static class Builder {
@@ -407,6 +427,7 @@
private boolean mRandomizedEncryptionRequired = true;
private boolean mUserAuthenticationRequired;
private int mUserAuthenticationValidityDurationSeconds = -1;
+ private boolean mUserAuthenticationValidWhileOnBody;
/**
* Creates a new instance of the {@code Builder}.
@@ -680,6 +701,34 @@
}
/**
+ * Sets whether the key is authorized for use after the authentication validity period is
+ * expired (see {@link #setUserAuthenticationValidityDurationSeconds} and {@link
+ * #setUserAuthenticationRequired}) if the device has a secure on-body sensor and if the
+ * device has not been removed from the user's body since the last successful
+ * authentication.
+ *
+ * <p>On devices that do not have a secure on-body sensor, creating a key with this
+ * parameter set to {@code true} will have no effect; the private or secret key will no
+ * longer be authorized for use after the validity period ends, and a fresh authentication
+ * will be required to use it again.
+ *
+ * <p>Note that "secure" on-body sensors are required by Android to have a secure path to
+ * the secure hardware, but the sensors themselves may not be difficult to fool. It is
+ * recommended that this feature be used to increase slightly the security of keys which
+ * would otherwise have to allow unauthenticated access, or have a very long validity
+ * period. Keys that require high assurance of user authorization should not use this
+ * feature and should set a short validity period.
+ *
+ * @param remainsValid if {@code true}, and if the device supports secure on-body detection,
+ * key will remain valid after authentication validity duration has expired.
+ */
+ @NonNull
+ public Builder setUserAuthenticationValidWhileOnBody(boolean remainsValid) {
+ mUserAuthenticationValidWhileOnBody = remainsValid;
+ return this;
+ }
+
+ /**
* Builds an instance of {@link KeyProtection}.
*
* @throws IllegalArgumentException if a required field is missing
@@ -697,7 +746,8 @@
mBlockModes,
mRandomizedEncryptionRequired,
mUserAuthenticationRequired,
- mUserAuthenticationValidityDurationSeconds);
+ mUserAuthenticationValidityDurationSeconds,
+ mUserAuthenticationValidWhileOnBody);
}
}
}
diff --git a/keystore/java/android/security/keystore/KeymasterUtils.java b/keystore/java/android/security/keystore/KeymasterUtils.java
index feafbfa..3a008bc 100644
--- a/keystore/java/android/security/keystore/KeymasterUtils.java
+++ b/keystore/java/android/security/keystore/KeymasterUtils.java
@@ -96,7 +96,8 @@
*/
public static void addUserAuthArgs(KeymasterArguments args,
boolean userAuthenticationRequired,
- int userAuthenticationValidityDurationSeconds) {
+ int userAuthenticationValidityDurationSeconds,
+ boolean userAuthenticationValidWhileOnBody) {
if (!userAuthenticationRequired) {
args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
return;
@@ -119,6 +120,10 @@
args.addUnsignedLong(KeymasterDefs.KM_TAG_USER_SECURE_ID,
KeymasterArguments.toUint64(fingerprintOnlySid));
args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, KeymasterDefs.HW_AUTH_FINGERPRINT);
+ if (userAuthenticationValidWhileOnBody) {
+ throw new ProviderException("Key validity extension while device is on-body is not "
+ + "supported for keys requiring fingerprint authentication");
+ }
} else {
// The key is authorized for use for the specified amount of time after the user has
// authenticated. Whatever unlocks the secure lock screen should authorize this key.
@@ -133,6 +138,9 @@
KeymasterDefs.HW_AUTH_PASSWORD | KeymasterDefs.HW_AUTH_FINGERPRINT);
args.addUnsignedInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT,
userAuthenticationValidityDurationSeconds);
+ if (userAuthenticationValidWhileOnBody) {
+ args.addBoolean(KeymasterDefs.KM_TAG_ALLOW_WHILE_ON_BODY);
+ }
}
}
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 6a565033..6257122 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -232,7 +232,6 @@
LOCAL_SRC_FILES += \
$(hwui_test_common_src_files) \
- tests/unit/BufferPoolTests.cpp \
tests/unit/CanvasStateTests.cpp \
tests/unit/ClipAreaTests.cpp \
tests/unit/CrashHandlerInjector.cpp \
diff --git a/libs/hwui/Animator.cpp b/libs/hwui/Animator.cpp
index 294edb6..7bd2b24 100644
--- a/libs/hwui/Animator.cpp
+++ b/libs/hwui/Animator.cpp
@@ -33,7 +33,6 @@
BaseRenderNodeAnimator::BaseRenderNodeAnimator(float finalValue)
: mTarget(nullptr)
- , mStagingTarget(nullptr)
, mFinalValue(finalValue)
, mDeltaValue(0)
, mFromValue(0)
@@ -43,8 +42,7 @@
, mStartTime(0)
, mDuration(300)
, mStartDelay(0)
- , mMayRunAsync(true)
- , mPlayTime(0) {
+ , mMayRunAsync(true) {
}
BaseRenderNodeAnimator::~BaseRenderNodeAnimator() {
@@ -83,129 +81,26 @@
}
void BaseRenderNodeAnimator::attach(RenderNode* target) {
- mStagingTarget = target;
+ mTarget = target;
onAttached();
}
-void BaseRenderNodeAnimator::start() {
- mStagingPlayState = PlayState::Running;
- mStagingRequests.push_back(Request::Start);
- onStagingPlayStateChanged();
-}
-
-void BaseRenderNodeAnimator::cancel() {
- mStagingPlayState = PlayState::Finished;
- mStagingRequests.push_back(Request::Cancel);
- onStagingPlayStateChanged();
-}
-
-void BaseRenderNodeAnimator::reset() {
- mStagingPlayState = PlayState::Finished;
- mStagingRequests.push_back(Request::Reset);
- onStagingPlayStateChanged();
-}
-
-void BaseRenderNodeAnimator::reverse() {
- mStagingPlayState = PlayState::Reversing;
- mStagingRequests.push_back(Request::Reverse);
- onStagingPlayStateChanged();
-}
-
-void BaseRenderNodeAnimator::end() {
- mStagingPlayState = PlayState::Finished;
- mStagingRequests.push_back(Request::End);
- onStagingPlayStateChanged();
-}
-
-void BaseRenderNodeAnimator::resolveStagingRequest(Request request) {
- switch (request) {
- case Request::Start:
- mPlayTime = (mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) ?
- mPlayTime : 0;
- mPlayState = PlayState::Running;
- break;
- case Request::Reverse:
- mPlayTime = (mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) ?
- mPlayTime : mDuration;
- mPlayState = PlayState::Reversing;
- break;
- case Request::Reset:
- mPlayTime = 0;
- mPlayState = PlayState::Finished;
- break;
- case Request::Cancel:
- mPlayState = PlayState::Finished;
- break;
- case Request::End:
- mPlayTime = mPlayState == PlayState::Reversing ? 0 : mDuration;
- mPlayState = PlayState::Finished;
- break;
- default:
- LOG_ALWAYS_FATAL("Invalid staging request: %d", static_cast<int>(request));
- };
-}
-
void BaseRenderNodeAnimator::pushStaging(AnimationContext& context) {
- if (mStagingTarget) {
- RenderNode* oldTarget = mTarget;
- mTarget = mStagingTarget;
- mStagingTarget = nullptr;
- if (oldTarget && oldTarget != mTarget) {
- oldTarget->onAnimatorTargetChanged(this);
- }
- }
-
if (!mHasStartValue) {
doSetStartValue(getValue(mTarget));
}
-
- if (!mStagingRequests.empty()) {
- // Keep track of the play state and play time before they are changed when
- // staging requests are resolved.
- nsecs_t currentPlayTime = mPlayTime;
- PlayState prevFramePlayState = mPlayState;
-
- // Resolve staging requests one by one.
- for (Request request : mStagingRequests) {
- resolveStagingRequest(request);
+ if (mStagingPlayState > mPlayState) {
+ if (mStagingPlayState == PlayState::Restarted) {
+ mStagingPlayState = PlayState::Running;
}
- mStagingRequests.clear();
-
- if (mStagingPlayState == PlayState::Finished) {
- // Set the staging play time and end the animation
- updatePlayTime(mPlayTime);
+ mPlayState = mStagingPlayState;
+ // Oh boy, we're starting! Man the battle stations!
+ if (mPlayState == PlayState::Running) {
+ transitionToRunning(context);
+ } else if (mPlayState == PlayState::Finished) {
callOnFinishedListener(context);
- } else if (mStagingPlayState == PlayState::Running
- || mStagingPlayState == PlayState::Reversing) {
- bool changed = currentPlayTime != mPlayTime || prevFramePlayState != mStagingPlayState;
- if (prevFramePlayState != mStagingPlayState) {
- transitionToRunning(context);
- }
- if (changed) {
- // Now we need to seek to the stagingPlayTime (i.e. the animation progress that was
- // requested from UI thread). It is achieved by modifying mStartTime, such that
- // current time - mStartTime = stagingPlayTime (or mDuration -stagingPlayTime in the
- // case of reversing)
- nsecs_t currentFrameTime = context.frameTimeMs();
- if (mPlayState == PlayState::Reversing) {
- // Reverse is not supported for animations with a start delay, so here we
- // assume no start delay.
- mStartTime = currentFrameTime - (mDuration - mPlayTime);
- } else {
- // Animation should play forward
- if (mPlayTime == 0) {
- // If the request is to start from the beginning, include start delay.
- mStartTime = currentFrameTime + mStartDelay;
- } else {
- // If the request is to seek to a non-zero play time, then we skip start
- // delay.
- mStartTime = currentFrameTime - mPlayTime;
- }
- }
- }
}
}
- onPushStaging();
}
void BaseRenderNodeAnimator::transitionToRunning(AnimationContext& context) {
@@ -241,37 +136,37 @@
// This should be set before setValue() so animators can query this time when setValue
// is called.
- nsecs_t currentPlayTime = context.frameTimeMs() - mStartTime;
- bool finished = updatePlayTime(currentPlayTime);
- if (finished && mPlayState != PlayState::Finished) {
- mPlayState = PlayState::Finished;
- callOnFinishedListener(context);
- }
- return finished;
-}
+ nsecs_t currentFrameTime = context.frameTimeMs();
+ onPlayTimeChanged(currentFrameTime - mStartTime);
-bool BaseRenderNodeAnimator::updatePlayTime(nsecs_t playTime) {
- mPlayTime = mPlayState == PlayState::Reversing ? mDuration - playTime : playTime;
- onPlayTimeChanged(mPlayTime);
// If BaseRenderNodeAnimator is handling the delay (not typical), then
// because the staging properties reflect the final value, we always need
// to call setValue even if the animation isn't yet running or is still
// being delayed as we need to override the staging value
- if (playTime < 0) {
+ if (mStartTime > context.frameTimeMs()) {
setValue(mTarget, mFromValue);
return false;
}
float fraction = 1.0f;
- if ((mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) && mDuration > 0) {
- fraction = mPlayTime / (float) mDuration;
+
+ if (mPlayState == PlayState::Running && mDuration > 0) {
+ fraction = (float)(currentFrameTime - mStartTime) / mDuration;
}
- fraction = MathUtils::clamp(fraction, 0.0f, 1.0f);
+ if (fraction >= 1.0f) {
+ fraction = 1.0f;
+ mPlayState = PlayState::Finished;
+ }
fraction = mInterpolator->interpolate(fraction);
setValue(mTarget, mFromValue + (mDeltaValue * fraction));
- return playTime >= mDuration;
+ if (mPlayState == PlayState::Finished) {
+ callOnFinishedListener(context);
+ return true;
+ }
+
+ return false;
}
void BaseRenderNodeAnimator::forceEndNow(AnimationContext& context) {
@@ -320,36 +215,18 @@
void RenderPropertyAnimator::onAttached() {
if (!mHasStartValue
- && mStagingTarget->isPropertyFieldDirty(mPropertyAccess->dirtyMask)) {
- setStartValue((mStagingTarget->stagingProperties().*mPropertyAccess->getter)());
+ && mTarget->isPropertyFieldDirty(mPropertyAccess->dirtyMask)) {
+ setStartValue((mTarget->stagingProperties().*mPropertyAccess->getter)());
}
}
void RenderPropertyAnimator::onStagingPlayStateChanged() {
if (mStagingPlayState == PlayState::Running) {
- if (mStagingTarget) {
- (mStagingTarget->mutateStagingProperties().*mPropertyAccess->setter)(finalValue());
- } else {
- // In the case of start delay where stagingTarget has been sync'ed over and null'ed
- // we delay the properties update to push staging.
- mShouldUpdateStagingProperties = true;
- }
+ (mTarget->mutateStagingProperties().*mPropertyAccess->setter)(finalValue());
} else if (mStagingPlayState == PlayState::Finished) {
// We're being canceled, so make sure that whatever values the UI thread
// is observing for us is pushed over
- mShouldSyncPropertyFields = true;
- }
-}
-
-void RenderPropertyAnimator::onPushStaging() {
- if (mShouldUpdateStagingProperties) {
- (mTarget->mutateStagingProperties().*mPropertyAccess->setter)(finalValue());
- mShouldUpdateStagingProperties = false;
- }
-
- if (mShouldSyncPropertyFields) {
mTarget->setPropertyFieldsDirty(dirtyMask());
- mShouldSyncPropertyFields = false;
}
}
diff --git a/libs/hwui/Animator.h b/libs/hwui/Animator.h
index fdae0f3..2c9c9c3 100644
--- a/libs/hwui/Animator.h
+++ b/libs/hwui/Animator.h
@@ -24,8 +24,6 @@
#include "utils/Macros.h"
-#include <vector>
-
namespace android {
namespace uirenderer {
@@ -61,14 +59,14 @@
mMayRunAsync = mayRunAsync;
}
bool mayRunAsync() { return mMayRunAsync; }
- ANDROID_API void start();
- ANDROID_API void reset();
- ANDROID_API void reverse();
- // Terminates the animation at its current progress.
- ANDROID_API void cancel();
-
- // Terminates the animation and skip to the end of the animation.
- ANDROID_API void end();
+ ANDROID_API void start() {
+ if (mStagingPlayState == PlayState::NotStarted) {
+ mStagingPlayState = PlayState::Running;
+ } else {
+ mStagingPlayState = PlayState::Restarted;
+ }
+ onStagingPlayStateChanged(); }
+ ANDROID_API void end() { mStagingPlayState = PlayState::Finished; onStagingPlayStateChanged(); }
void attach(RenderNode* target);
virtual void onAttached() {}
@@ -76,42 +74,36 @@
void pushStaging(AnimationContext& context);
bool animate(AnimationContext& context);
- bool isRunning() { return mPlayState == PlayState::Running
- || mPlayState == PlayState::Reversing; }
+ bool isRunning() { return mPlayState == PlayState::Running; }
bool isFinished() { return mPlayState == PlayState::Finished; }
float finalValue() { return mFinalValue; }
ANDROID_API virtual uint32_t dirtyMask() = 0;
void forceEndNow(AnimationContext& context);
- RenderNode* target() { return mTarget; }
- RenderNode* stagingTarget() { return mStagingTarget; }
protected:
// PlayState is used by mStagingPlayState and mPlayState to track the state initiated from UI
// thread and Render Thread animation state, respectively.
// From the UI thread, mStagingPlayState transition looks like
- // NotStarted -> Running/Reversing -> Finished
- // ^ |
- // | |
- // ----------------------
+ // NotStarted -> Running -> Finished
+ // ^ |
+ // | |
+ // Restarted <------
// Note: For mStagingState, the Finished state (optional) is only set when the animation is
// terminated by user.
//
// On Render Thread, mPlayState transition:
- // NotStart -> Running/Reversing-> Finished
- // ^ |
- // | |
- // ------------------
- // Note that if the animation is in Running/Reversing state, calling start or reverse again
- // would do nothing if the animation has the same play direction as the request; otherwise,
- // the animation would start from where it is and change direction (i.e. Reversing <-> Running)
+ // NotStart -> Running -> Finished
+ // ^ |
+ // | |
+ // -------------
enum class PlayState {
NotStarted,
Running,
- Reversing,
Finished,
+ Restarted,
};
BaseRenderNodeAnimator(float finalValue);
@@ -119,15 +111,14 @@
virtual float getValue(RenderNode* target) const = 0;
virtual void setValue(RenderNode* target, float value) = 0;
+ RenderNode* target() { return mTarget; }
void callOnFinishedListener(AnimationContext& context);
virtual void onStagingPlayStateChanged() {}
virtual void onPlayTimeChanged(nsecs_t playTime) {}
- virtual void onPushStaging() {}
RenderNode* mTarget;
- RenderNode* mStagingTarget;
float mFinalValue;
float mDeltaValue;
@@ -141,28 +132,13 @@
nsecs_t mDuration;
nsecs_t mStartDelay;
bool mMayRunAsync;
- // Play Time tracks the progress of animation, it should always be [0, mDuration], 0 being
- // the beginning of the animation, will reach mDuration at the end of an animation.
- nsecs_t mPlayTime;
sp<AnimationListener> mListener;
private:
- enum class Request {
- Start,
- Reverse,
- Reset,
- Cancel,
- End
- };
inline void checkMutable();
virtual void transitionToRunning(AnimationContext& context);
void doSetStartValue(float value);
- bool updatePlayTime(nsecs_t playTime);
- void resolveStagingRequest(Request request);
-
- std::vector<Request> mStagingRequests;
-
};
class RenderPropertyAnimator : public BaseRenderNodeAnimator {
@@ -191,7 +167,6 @@
virtual void setValue(RenderNode* target, float value) override;
virtual void onAttached() override;
virtual void onStagingPlayStateChanged() override;
- virtual void onPushStaging() override;
private:
typedef bool (RenderProperties::*SetFloatProperty)(float value);
@@ -201,8 +176,6 @@
const PropertyAccessors* mPropertyAccess;
static const PropertyAccessors PROPERTY_ACCESSOR_LUT[];
- bool mShouldSyncPropertyFields = false;
- bool mShouldUpdateStagingProperties = false;
};
class CanvasPropertyPrimitiveAnimator : public BaseRenderNodeAnimator {
diff --git a/libs/hwui/AnimatorManager.cpp b/libs/hwui/AnimatorManager.cpp
index 2198fcc..cd30b18 100644
--- a/libs/hwui/AnimatorManager.cpp
+++ b/libs/hwui/AnimatorManager.cpp
@@ -27,8 +27,9 @@
using namespace std;
-static void detach(sp<BaseRenderNodeAnimator>& animator) {
+static void unref(BaseRenderNodeAnimator* animator) {
animator->detach();
+ animator->decStrong(nullptr);
}
AnimatorManager::AnimatorManager(RenderNode& parent)
@@ -37,28 +38,14 @@
}
AnimatorManager::~AnimatorManager() {
- for_each(mNewAnimators.begin(), mNewAnimators.end(), detach);
- for_each(mAnimators.begin(), mAnimators.end(), detach);
+ for_each(mNewAnimators.begin(), mNewAnimators.end(), unref);
+ for_each(mAnimators.begin(), mAnimators.end(), unref);
}
void AnimatorManager::addAnimator(const sp<BaseRenderNodeAnimator>& animator) {
- RenderNode* stagingTarget = animator->stagingTarget();
- if (stagingTarget == &mParent) {
- return;
- }
- mNewAnimators.emplace_back(animator.get());
- // If the animator is already attached to other RenderNode, remove it from that RenderNode's
- // new animator list. This ensures one animator only ends up in one newAnimatorList during one
- // frame, even when it's added multiple times to multiple targets.
- if (stagingTarget) {
- stagingTarget->removeAnimator(animator);
- }
+ animator->incStrong(nullptr);
animator->attach(&mParent);
-}
-
-void AnimatorManager::removeAnimator(const sp<BaseRenderNodeAnimator>& animator) {
- mNewAnimators.erase(std::remove(mNewAnimators.begin(), mNewAnimators.end(), animator),
- mNewAnimators.end());
+ mNewAnimators.push_back(animator.get());
}
void AnimatorManager::setAnimationHandle(AnimationHandle* handle) {
@@ -69,40 +56,38 @@
&mParent, mParent.getName());
}
+template<typename T>
+static void move_all(T& source, T& dest) {
+ dest.reserve(source.size() + dest.size());
+ for (typename T::iterator it = source.begin(); it != source.end(); it++) {
+ dest.push_back(*it);
+ }
+ source.clear();
+}
+
void AnimatorManager::pushStaging() {
if (mNewAnimators.size()) {
LOG_ALWAYS_FATAL_IF(!mAnimationHandle,
"Trying to start new animators on %p (%s) without an animation handle!",
&mParent, mParent.getName());
-
- // Only add new animators that are not already in the mAnimators list
- for (auto& anim : mNewAnimators) {
- if (anim->target() != &mParent) {
- mAnimators.push_back(std::move(anim));
- }
- }
- mNewAnimators.clear();
+ // Since this is a straight move, we don't need to inc/dec the ref count
+ move_all(mNewAnimators, mAnimators);
}
- for (auto& animator : mAnimators) {
- animator->pushStaging(mAnimationHandle->context());
+ for (vector<BaseRenderNodeAnimator*>::iterator it = mAnimators.begin(); it != mAnimators.end(); it++) {
+ (*it)->pushStaging(mAnimationHandle->context());
}
}
-void AnimatorManager::onAnimatorTargetChanged(BaseRenderNodeAnimator* animator) {
- LOG_ALWAYS_FATAL_IF(animator->target() == &mParent, "Target has not been changed");
- mAnimators.erase(std::remove(mAnimators.begin(), mAnimators.end(), animator), mAnimators.end());
-}
-
class AnimateFunctor {
public:
AnimateFunctor(TreeInfo& info, AnimationContext& context)
: dirtyMask(0), mInfo(info), mContext(context) {}
- bool operator() (sp<BaseRenderNodeAnimator>& animator) {
+ bool operator() (BaseRenderNodeAnimator* animator) {
dirtyMask |= animator->dirtyMask();
bool remove = animator->animate(mContext);
if (remove) {
- animator->detach();
+ animator->decStrong(nullptr);
} else {
if (animator->isRunning()) {
mInfo.out.hasAnimations = true;
@@ -144,18 +129,20 @@
uint32_t AnimatorManager::animateCommon(TreeInfo& info) {
AnimateFunctor functor(info, mAnimationHandle->context());
- auto newEnd = std::remove_if(mAnimators.begin(), mAnimators.end(), functor);
+ std::vector< BaseRenderNodeAnimator* >::iterator newEnd;
+ newEnd = std::remove_if(mAnimators.begin(), mAnimators.end(), functor);
mAnimators.erase(newEnd, mAnimators.end());
mAnimationHandle->notifyAnimationsRan();
mParent.mProperties.updateMatrix();
return functor.dirtyMask;
}
-static void endStagingAnimator(sp<BaseRenderNodeAnimator>& animator) {
- animator->cancel();
+static void endStagingAnimator(BaseRenderNodeAnimator* animator) {
+ animator->end();
if (animator->listener()) {
- animator->listener()->onAnimationFinished(animator.get());
+ animator->listener()->onAnimationFinished(animator);
}
+ animator->decStrong(nullptr);
}
void AnimatorManager::endAllStagingAnimators() {
@@ -170,8 +157,9 @@
public:
EndActiveAnimatorsFunctor(AnimationContext& context) : mContext(context) {}
- void operator() (sp<BaseRenderNodeAnimator>& animator) {
+ void operator() (BaseRenderNodeAnimator* animator) {
animator->forceEndNow(mContext);
+ animator->decStrong(nullptr);
}
private:
diff --git a/libs/hwui/AnimatorManager.h b/libs/hwui/AnimatorManager.h
index 61f6179..fb75eb8 100644
--- a/libs/hwui/AnimatorManager.h
+++ b/libs/hwui/AnimatorManager.h
@@ -39,13 +39,11 @@
~AnimatorManager();
void addAnimator(const sp<BaseRenderNodeAnimator>& animator);
- void removeAnimator(const sp<BaseRenderNodeAnimator>& animator);
void setAnimationHandle(AnimationHandle* handle);
bool hasAnimationHandle() { return mAnimationHandle; }
void pushStaging();
- void onAnimatorTargetChanged(BaseRenderNodeAnimator* animator);
// Returns the combined dirty mask of all animators run
uint32_t animate(TreeInfo& info);
@@ -68,8 +66,9 @@
AnimationHandle* mAnimationHandle;
// To improve the efficiency of resizing & removing from the vector
- std::vector< sp<BaseRenderNodeAnimator> > mNewAnimators;
- std::vector< sp<BaseRenderNodeAnimator> > mAnimators;
+ // use manual ref counting instead of sp<>.
+ std::vector<BaseRenderNodeAnimator*> mNewAnimators;
+ std::vector<BaseRenderNodeAnimator*> mAnimators;
};
} /* namespace uirenderer */
diff --git a/libs/hwui/BufferPool.h b/libs/hwui/BufferPool.h
deleted file mode 100644
index 005b399..0000000
--- a/libs/hwui/BufferPool.h
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include "utils/RefBase.h"
-#include "utils/Log.h"
-#include "utils/Macros.h"
-
-#include <atomic>
-#include <stdint.h>
-#include <memory>
-#include <mutex>
-
-namespace android {
-namespace uirenderer {
-
-/*
- * Simple thread-safe pool of int64_t arrays of a provided size.
- *
- * Permits allocating a client-provided max number of buffers.
- * If all buffers are in use, refuses to service any more
- * acquire requests until buffers are re-released to the pool.
- */
-class BufferPool : public VirtualLightRefBase {
-public:
- class Buffer {
- PREVENT_COPY_AND_ASSIGN(Buffer);
- public:
- int64_t* getBuffer() { return mBuffer.get(); }
- size_t getSize() { return mSize; }
-
- void release() {
- LOG_ALWAYS_FATAL_IF(mPool.get() == nullptr, "attempt to release unacquired buffer");
- mPool->release(this);
- }
-
- Buffer* incRef() {
- mRefs++;
- return this;
- }
-
- int decRef() {
- int refs = mRefs.fetch_sub(1);
- LOG_ALWAYS_FATAL_IF(refs == 0, "buffer reference decremented below 0");
- return refs - 1;
- }
-
- bool isUniqueRef() {
- return mRefs.load() == 1;
- }
-
- private:
- friend class BufferPool;
-
- Buffer(BufferPool* pool, size_t size) : mRefs(1) {
- mSize = size;
- mBuffer.reset(new int64_t[size]);
- mPool = pool;
- }
-
- void setPool(BufferPool* pool) {
- mPool = pool;
- }
-
- std::unique_ptr<Buffer> mNext;
- std::unique_ptr<int64_t[]> mBuffer;
- sp<BufferPool> mPool;
- size_t mSize;
-
- std::atomic_int mRefs;
- };
-
- BufferPool(size_t bufferSize, size_t count)
- : mBufferSize(bufferSize), mCount(count) {}
-
- /**
- * Acquires a buffer from the buffer pool if available.
- *
- * Only `mCount` buffers are allowed to be in use at a single
- * instance.
- *
- * If no buffer is available, i.e. `mCount` buffers are in use,
- * returns nullptr.
- *
- * The pointer returned from this method *MUST NOT* be freed, instead
- * BufferPool::release() must be called upon it when the client
- * is done with it. Failing to release buffers will eventually make the
- * BufferPool refuse to service any more BufferPool::acquire() requests.
- */
- BufferPool::Buffer* acquire() {
- std::lock_guard<std::mutex> lock(mLock);
-
- if (mHead.get() != nullptr) {
- BufferPool::Buffer* res = mHead.release();
- mHead = std::move(res->mNext);
- res->mNext.reset(nullptr);
- res->setPool(this);
- res->incRef();
- return res;
- }
-
- if (mAllocatedCount < mCount) {
- ++mAllocatedCount;
- return new BufferPool::Buffer(this, mBufferSize);
- }
-
- return nullptr;
- }
-
- /**
- * Releases a buffer previously acquired by BufferPool::acquire().
- *
- * The released buffer is not valid after calling this method and
- * attempting to use will result in undefined behavior.
- */
- void release(BufferPool::Buffer* buffer) {
- std::lock_guard<std::mutex> lock(mLock);
-
- if (buffer->decRef() != 0) {
- return;
- }
-
- buffer->setPool(nullptr);
-
- BufferPool::Buffer* list = mHead.get();
- if (list == nullptr) {
- mHead.reset(buffer);
- mHead->mNext.reset(nullptr);
- return;
- }
-
- while (list->mNext.get() != nullptr) {
- list = list->mNext.get();
- }
-
- list->mNext.reset(buffer);
- }
-
- /*
- * Used for testing.
- */
- size_t getAvailableBufferCount() {
- size_t remainingToAllocateCount = mCount - mAllocatedCount;
-
- BufferPool::Buffer* list = mHead.get();
- if (list == nullptr) return remainingToAllocateCount;
-
- int count = 1;
- while (list->mNext.get() != nullptr) {
- count++;
- list = list->mNext.get();
- }
-
- return count + remainingToAllocateCount;
- }
-
-private:
- mutable std::mutex mLock;
-
- size_t mBufferSize;
- size_t mCount;
- size_t mAllocatedCount = 0;
- std::unique_ptr<BufferPool::Buffer> mHead;
-};
-
-}; // namespace uirenderer
-}; // namespace android
diff --git a/libs/hwui/FrameMetricsObserver.h b/libs/hwui/FrameMetricsObserver.h
index 2b42a80..4f81c86 100644
--- a/libs/hwui/FrameMetricsObserver.h
+++ b/libs/hwui/FrameMetricsObserver.h
@@ -18,14 +18,12 @@
#include <utils/RefBase.h>
-#include "BufferPool.h"
-
namespace android {
namespace uirenderer {
class FrameMetricsObserver : public VirtualLightRefBase {
public:
- virtual void notify(BufferPool::Buffer* buffer, int dropCount);
+ virtual void notify(const int64_t* buffer);
};
}; // namespace uirenderer
diff --git a/libs/hwui/FrameMetricsReporter.h b/libs/hwui/FrameMetricsReporter.h
index 0831d24..c1cd0a92 100644
--- a/libs/hwui/FrameMetricsReporter.h
+++ b/libs/hwui/FrameMetricsReporter.h
@@ -19,7 +19,6 @@
#include <utils/RefBase.h>
#include <utils/Log.h>
-#include "BufferPool.h"
#include "FrameInfo.h"
#include "FrameMetricsObserver.h"
@@ -31,10 +30,7 @@
class FrameMetricsReporter {
public:
- FrameMetricsReporter() {
- mBufferPool = new BufferPool(kBufferSize, kBufferCount);
- LOG_ALWAYS_FATAL_IF(mBufferPool.get() == nullptr, "OOM: unable to allocate buffer pool");
- }
+ FrameMetricsReporter() {}
void addObserver(FrameMetricsObserver* observer) {
mObservers.push_back(observer);
@@ -55,36 +51,13 @@
}
void reportFrameMetrics(const int64_t* stats) {
- BufferPool::Buffer* statsBuffer = mBufferPool->acquire();
-
- if (statsBuffer != nullptr) {
- // copy in frame stats
- memcpy(statsBuffer->getBuffer(), stats, kBufferSize * sizeof(*stats));
-
- // notify on requested threads
- for (size_t i = 0; i < mObservers.size(); i++) {
- mObservers[i]->notify(statsBuffer, mDroppedReports);
- }
-
- // drop our reference
- statsBuffer->release();
- mDroppedReports = 0;
- } else {
- mDroppedReports++;
+ for (size_t i = 0; i < mObservers.size(); i++) {
+ mObservers[i]->notify(stats);
}
}
- int getDroppedReports() { return mDroppedReports; }
-
private:
- static const size_t kBufferCount = 3;
- static const size_t kBufferSize = static_cast<size_t>(FrameInfoIndex::NumIndexes);
-
std::vector< sp<FrameMetricsObserver> > mObservers;
-
- sp<BufferPool> mBufferPool;
-
- int mDroppedReports = 0;
};
}; // namespace uirenderer
diff --git a/libs/hwui/PropertyValuesAnimatorSet.cpp b/libs/hwui/PropertyValuesAnimatorSet.cpp
index b29f91f..eca1afcc 100644
--- a/libs/hwui/PropertyValuesAnimatorSet.cpp
+++ b/libs/hwui/PropertyValuesAnimatorSet.cpp
@@ -17,8 +17,6 @@
#include "PropertyValuesAnimatorSet.h"
#include "RenderNode.h"
-#include <algorithm>
-
namespace android {
namespace uirenderer {
@@ -55,26 +53,16 @@
}
void PropertyValuesAnimatorSet::onPlayTimeChanged(nsecs_t playTime) {
- if (playTime == 0 && mDuration > 0) {
- // Reset all the animators
- for (auto it = mAnimators.rbegin(); it != mAnimators.rend(); it++) {
- // Note that this set may containing animators modifying the same property, so when we
- // reset the animators, we need to make sure the animators that end the first will
- // have the final say on what the property value should be.
- (*it)->setFraction(0);
- }
- } else if (playTime >= mDuration) {
- // Skip all the animators to end
- for (auto& anim : mAnimators) {
- anim->setFraction(1);
- }
- } else {
- for (auto& anim : mAnimators) {
- anim->setCurrentPlayTime(playTime);
- }
+ for (size_t i = 0; i < mAnimators.size(); i++) {
+ mAnimators[i]->setCurrentPlayTime(playTime);
}
}
+void PropertyValuesAnimatorSet::reset() {
+ // TODO: implement reset through adding a play state because we need to support reset() even
+ // during an animation run.
+}
+
void PropertyValuesAnimatorSet::start(AnimationListener* listener) {
init();
mOneShotListener = listener;
@@ -82,23 +70,20 @@
}
void PropertyValuesAnimatorSet::reverse(AnimationListener* listener) {
- init();
- mOneShotListener = listener;
- BaseRenderNodeAnimator::reverse();
+// TODO: implement reverse
}
void PropertyValuesAnimatorSet::init() {
if (mInitialized) {
return;
}
-
- // Sort the animators by their total duration. Note that all the animators in the set start at
- // the same time, so the ones with longer total duration (which includes start delay) will
- // be the ones that end later.
- std::sort(mAnimators.begin(), mAnimators.end(), [](auto& a, auto&b) {
- return a->getTotalDuration() < b->getTotalDuration();
- });
- mDuration = mAnimators[mAnimators.size() - 1]->getTotalDuration();
+ nsecs_t maxDuration = 0;
+ for (size_t i = 0; i < mAnimators.size(); i++) {
+ if (maxDuration < mAnimators[i]->getTotalDuration()) {
+ maxDuration = mAnimators[i]->getTotalDuration();
+ }
+ }
+ mDuration = maxDuration;
mInitialized = true;
}
@@ -121,19 +106,18 @@
void PropertyAnimator::setCurrentPlayTime(nsecs_t playTime) {
if (playTime >= mStartDelay && playTime < mTotalDuration) {
nsecs_t currentIterationPlayTime = (playTime - mStartDelay) % mDuration;
- float fraction = currentIterationPlayTime / (float) mDuration;
- setFraction(fraction);
+ mLatestFraction = currentIterationPlayTime / (float) mDuration;
} else if (mLatestFraction < 1.0f && playTime >= mTotalDuration) {
- // This makes sure we only set the fraction = 1 once. It is needed because there might
- // be another animator modifying the same property after this animator finishes, we need
- // to make sure we don't set conflicting values on the same property within one frame.
- setFraction(1.0f);
+ mLatestFraction = 1.0f;
+ } else {
+ return;
}
+
+ setFraction(mLatestFraction);
}
void PropertyAnimator::setFraction(float fraction) {
- mLatestFraction = fraction;
- float interpolatedFraction = mInterpolator->interpolate(fraction);
+ float interpolatedFraction = mInterpolator->interpolate(mLatestFraction);
mPropertyValuesHolder->setFraction(interpolatedFraction);
}
diff --git a/libs/hwui/PropertyValuesAnimatorSet.h b/libs/hwui/PropertyValuesAnimatorSet.h
index c7ae7c0..4c7ce52 100644
--- a/libs/hwui/PropertyValuesAnimatorSet.h
+++ b/libs/hwui/PropertyValuesAnimatorSet.h
@@ -50,6 +50,7 @@
void start(AnimationListener* listener);
void reverse(AnimationListener* listener);
+ void reset();
void addPropertyAnimator(PropertyValuesHolder* propertyValuesHolder,
Interpolator* interpolators, int64_t startDelays,
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 9ac76a4..bade216 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -218,10 +218,6 @@
mAnimatorManager.addAnimator(animator);
}
-void RenderNode::removeAnimator(const sp<BaseRenderNodeAnimator>& animator) {
- mAnimatorManager.removeAnimator(animator);
-}
-
void RenderNode::damageSelf(TreeInfo& info) {
if (isRenderable()) {
if (properties().getClipDamageToBounds()) {
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index e037645..f248de54 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -187,12 +187,6 @@
// UI thread only!
ANDROID_API void addAnimator(const sp<BaseRenderNodeAnimator>& animator);
- void removeAnimator(const sp<BaseRenderNodeAnimator>& animator);
-
- // This can only happen during pushStaging()
- void onAnimatorTargetChanged(BaseRenderNodeAnimator* animator) {
- mAnimatorManager.onAnimatorTargetChanged(animator);
- }
AnimatorManager& animators() { return mAnimatorManager; }
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 1f81970..cb61e51 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -159,14 +159,6 @@
}
}
- long getDroppedFrameReportCount() {
- if (mFrameMetricsReporter.get() != nullptr) {
- return mFrameMetricsReporter->getDroppedReports();
- }
-
- return 0;
- }
-
private:
friend class RegisterFrameCallbackTask;
// TODO: Replace with something better for layer & other GL object
diff --git a/libs/hwui/tests/unit/BufferPoolTests.cpp b/libs/hwui/tests/unit/BufferPoolTests.cpp
deleted file mode 100644
index 44e6d3a..0000000
--- a/libs/hwui/tests/unit/BufferPoolTests.cpp
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <gtest/gtest.h>
-
-#include <BufferPool.h>
-#include <utils/StrongPointer.h>
-
-namespace android {
-namespace uirenderer {
-
-TEST(BufferPool, acquireThenRelease) {
- static const int numRuns = 5;
-
- // 10 buffers of size 1
- static const size_t bufferSize = 1;
- static const size_t bufferCount = 10;
- sp<BufferPool> pool = new BufferPool(bufferSize, bufferCount);
-
- for (int run = 0; run < numRuns; run++) {
- BufferPool::Buffer* acquiredBuffers[bufferCount];
- for (size_t i = 0; i < bufferCount; i++) {
- ASSERT_EQ(bufferCount - i, pool->getAvailableBufferCount());
- acquiredBuffers[i] = pool->acquire();
- ASSERT_NE(nullptr, acquiredBuffers[i]);
- ASSERT_TRUE(acquiredBuffers[i]->isUniqueRef());
- }
-
- for (size_t i = 0; i < bufferCount; i++) {
- ASSERT_EQ(i, pool->getAvailableBufferCount());
- acquiredBuffers[i]->release();
- acquiredBuffers[i] = nullptr;
- }
-
- ASSERT_EQ(bufferCount, pool->getAvailableBufferCount());
- }
-}
-
-TEST(BufferPool, acquireReleaseInterleaved) {
- static const int numRuns = 5;
-
- // 10 buffers of size 1
- static const size_t bufferSize = 1;
- static const size_t bufferCount = 10;
-
- sp<BufferPool> pool = new BufferPool(bufferSize, bufferCount);
-
- for (int run = 0; run < numRuns; run++) {
- BufferPool::Buffer* acquiredBuffers[bufferCount];
-
- // acquire all
- for (size_t i = 0; i < bufferCount; i++) {
- ASSERT_EQ(bufferCount - i, pool->getAvailableBufferCount());
- acquiredBuffers[i] = pool->acquire();
- ASSERT_NE(nullptr, acquiredBuffers[i]);
- }
-
- // release half
- for (size_t i = 0; i < bufferCount / 2; i++) {
- ASSERT_EQ(i, pool->getAvailableBufferCount());
- acquiredBuffers[i]->release();
- acquiredBuffers[i] = nullptr;
- }
-
- const size_t expectedRemaining = bufferCount / 2;
- ASSERT_EQ(expectedRemaining, pool->getAvailableBufferCount());
-
- // acquire half
- for (size_t i = 0; i < bufferCount / 2; i++) {
- ASSERT_EQ(expectedRemaining - i, pool->getAvailableBufferCount());
- acquiredBuffers[i] = pool->acquire();
- }
-
- // acquire one more, should fail
- ASSERT_EQ(nullptr, pool->acquire());
-
- // release all
- for (size_t i = 0; i < bufferCount; i++) {
- ASSERT_EQ(i, pool->getAvailableBufferCount());
- acquiredBuffers[i]->release();
- acquiredBuffers[i] = nullptr;
- }
-
- ASSERT_EQ(bufferCount, pool->getAvailableBufferCount());
- }
-}
-
-};
-};
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index f09f654..60444e0 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -277,6 +277,30 @@
public static final int HOTWORD = 1999;
}
+ // TODO make AudioSource static (API change) and move this method inside the AudioSource class
+ /**
+ * @hide
+ * @param source An audio source to test
+ * @return true if the source is only visible to system components
+ */
+ public static boolean isSystemOnlyAudioSource(int source) {
+ switch(source) {
+ case AudioSource.DEFAULT:
+ case AudioSource.MIC:
+ case AudioSource.VOICE_UPLINK:
+ case AudioSource.VOICE_DOWNLINK:
+ case AudioSource.VOICE_CALL:
+ case AudioSource.CAMCORDER:
+ case AudioSource.VOICE_RECOGNITION:
+ case AudioSource.VOICE_COMMUNICATION:
+ //case REMOTE_SUBMIX: considered "system" as it requires system permissions
+ case AudioSource.UNPROCESSED:
+ return false;
+ default:
+ return true;
+ }
+ }
+
/**
* Defines the video source. These constants are used with
* {@link MediaRecorder#setVideoSource(int)}.
diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
index 707db06..8f022db 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
@@ -16,12 +16,17 @@
package android.media.soundtrigger;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
import android.hardware.soundtrigger.SoundTrigger;
+import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
+import android.media.AudioFormat;
import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
import android.os.ParcelUuid;
import android.os.RemoteException;
import android.util.Slog;
@@ -29,6 +34,8 @@
import com.android.internal.app.ISoundTriggerService;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.UUID;
/**
@@ -45,6 +52,12 @@
private static final boolean DBG = false;
private static final String TAG = "SoundTriggerDetector";
+ private static final int MSG_AVAILABILITY_CHANGED = 1;
+ private static final int MSG_SOUND_TRIGGER_DETECTED = 2;
+ private static final int MSG_DETECTION_ERROR = 3;
+ private static final int MSG_DETECTION_PAUSE = 4;
+ private static final int MSG_DETECTION_RESUME = 5;
+
private final Object mLock = new Object();
private final ISoundTriggerService mSoundTriggerService;
@@ -53,7 +66,121 @@
private final Handler mHandler;
private final RecognitionCallback mRecognitionCallback;
- public abstract class Callback {
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true,
+ value = {
+ RECOGNITION_FLAG_NONE,
+ RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO,
+ RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS
+ })
+ public @interface RecognitionFlags {}
+
+ /**
+ * Empty flag for {@link #startRecognition(int)}.
+ *
+ * @hide
+ */
+ public static final int RECOGNITION_FLAG_NONE = 0;
+
+ /**
+ * Recognition flag for {@link #startRecognition(int)} that indicates
+ * whether the trigger audio for hotword needs to be captured.
+ */
+ public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 0x1;
+
+ /**
+ * Recognition flag for {@link #startRecognition(int)} that indicates
+ * whether the recognition should keep going on even after the
+ * model triggers.
+ * If this flag is specified, it's possible to get multiple
+ * triggers after a call to {@link #startRecognition(int)}, if the model
+ * triggers multiple times.
+ * When this isn't specified, the default behavior is to stop recognition once the
+ * trigger happenss, till the caller starts recognition again.
+ */
+ public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 0x2;
+
+ /**
+ * Additional payload for {@link Callback#onDetected}.
+ */
+ public static class EventPayload {
+ private final boolean mTriggerAvailable;
+
+ // Indicates if {@code captureSession} can be used to continue capturing more audio
+ // from the DSP hardware.
+ private final boolean mCaptureAvailable;
+ // The session to use when attempting to capture more audio from the DSP hardware.
+ private final int mCaptureSession;
+ private final AudioFormat mAudioFormat;
+ // Raw data associated with the event.
+ // This is the audio that triggered the keyphrase if {@code isTriggerAudio} is true.
+ private final byte[] mData;
+
+ private EventPayload(boolean triggerAvailable, boolean captureAvailable,
+ AudioFormat audioFormat, int captureSession, byte[] data) {
+ mTriggerAvailable = triggerAvailable;
+ mCaptureAvailable = captureAvailable;
+ mCaptureSession = captureSession;
+ mAudioFormat = audioFormat;
+ mData = data;
+ }
+
+ /**
+ * Gets the format of the audio obtained using {@link #getTriggerAudio()}.
+ * May be null if there's no audio present.
+ */
+ @Nullable
+ public AudioFormat getCaptureAudioFormat() {
+ return mAudioFormat;
+ }
+
+ /**
+ * Gets the raw audio that triggered the keyphrase.
+ * This may be null if the trigger audio isn't available.
+ * If non-null, the format of the audio can be obtained by calling
+ * {@link #getCaptureAudioFormat()}.
+ *
+ * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
+ */
+ @Nullable
+ public byte[] getTriggerAudio() {
+ if (mTriggerAvailable) {
+ return mData;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Gets the session ID to start a capture from the DSP.
+ * This may be null if streaming capture isn't possible.
+ * If non-null, the format of the audio that can be captured can be
+ * obtained using {@link #getCaptureAudioFormat()}.
+ *
+ * TODO: Candidate for Public API when the API to start capture with a session ID
+ * is made public.
+ *
+ * TODO: Add this to {@link #getCaptureAudioFormat()}:
+ * "Gets the format of the audio obtained using {@link #getTriggerAudio()}
+ * or {@link #getCaptureSession()}. May be null if no audio can be obtained
+ * for either the trigger or a streaming session."
+ *
+ * TODO: Should this return a known invalid value instead?
+ *
+ * @hide
+ */
+ @Nullable
+ public Integer getCaptureSession() {
+ if (mCaptureAvailable) {
+ return mCaptureSession;
+ } else {
+ return null;
+ }
+ }
+ }
+
+ public static abstract class Callback {
/**
* Called when the availability of the sound model changes.
*/
@@ -63,7 +190,7 @@
* Called when the sound model has triggered (such as when it matched a
* given sound pattern).
*/
- public abstract void onDetected();
+ public abstract void onDetected(@NonNull EventPayload eventPayload);
/**
* Called when the detection fails due to an error.
@@ -95,9 +222,9 @@
mSoundModelId = soundModelId;
mCallback = callback;
if (handler == null) {
- mHandler = new Handler();
+ mHandler = new MyHandler();
} else {
- mHandler = handler;
+ mHandler = new MyHandler(handler.getLooper());
}
mRecognitionCallback = new RecognitionCallback();
}
@@ -107,13 +234,19 @@
* {@link Callback}.
* @return Indicates whether the call succeeded or not.
*/
- public boolean startRecognition() {
+ public boolean startRecognition(@RecognitionFlags int recognitionFlags) {
if (DBG) {
Slog.d(TAG, "startRecognition()");
}
+ boolean captureTriggerAudio =
+ (recognitionFlags & RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO) != 0;
+
+ boolean allowMultipleTriggers =
+ (recognitionFlags & RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS) != 0;
try {
mSoundTriggerService.startRecognition(new ParcelUuid(mSoundModelId),
- mRecognitionCallback);
+ mRecognitionCallback, new RecognitionConfig(captureTriggerAudio,
+ allowMultipleTriggers, null, null));
} catch (RemoteException e) {
return false;
}
@@ -144,17 +277,25 @@
/**
* Callback that handles events from the lower sound trigger layer.
+ *
+ * Note that these callbacks will be called synchronously from the SoundTriggerService
+ * layer and thus should do minimal work (such as sending a message on a handler to do
+ * the real work).
* @hide
*/
- private static class RecognitionCallback extends
- IRecognitionStatusCallback.Stub {
+ private class RecognitionCallback extends IRecognitionStatusCallback.Stub {
/**
* @hide
*/
@Override
public void onDetected(SoundTrigger.RecognitionEvent event) {
- Slog.e(TAG, "onDetected()" + event);
+ Slog.d(TAG, "onDetected()" + event);
+ Message.obtain(mHandler,
+ MSG_SOUND_TRIGGER_DETECTED,
+ new EventPayload(event.triggerInData, event.captureAvailable,
+ event.captureFormat, event.captureSession, event.data))
+ .sendToTarget();
}
/**
@@ -162,7 +303,8 @@
*/
@Override
public void onError(int status) {
- Slog.e(TAG, "onError()" + status);
+ Slog.d(TAG, "onError()" + status);
+ mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
}
/**
@@ -170,7 +312,8 @@
*/
@Override
public void onRecognitionPaused() {
- Slog.e(TAG, "onRecognitionPaused()");
+ Slog.d(TAG, "onRecognitionPaused()");
+ mHandler.sendEmptyMessage(MSG_DETECTION_PAUSE);
}
/**
@@ -178,7 +321,44 @@
*/
@Override
public void onRecognitionResumed() {
- Slog.e(TAG, "onRecognitionResumed()");
+ Slog.d(TAG, "onRecognitionResumed()");
+ mHandler.sendEmptyMessage(MSG_DETECTION_RESUME);
+ }
+ }
+
+ private class MyHandler extends Handler {
+
+ MyHandler() {
+ super();
+ }
+
+ MyHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (mCallback == null) {
+ Slog.w(TAG, "Received message: " + msg.what + " for NULL callback.");
+ return;
+ }
+ switch (msg.what) {
+ case MSG_SOUND_TRIGGER_DETECTED:
+ mCallback.onDetected((EventPayload) msg.obj);
+ break;
+ case MSG_DETECTION_ERROR:
+ mCallback.onError();
+ break;
+ case MSG_DETECTION_PAUSE:
+ mCallback.onRecognitionPaused();
+ break;
+ case MSG_DETECTION_RESUME:
+ mCallback.onRecognitionResumed();
+ break;
+ default:
+ super.handleMessage(msg);
+
+ }
}
}
}
diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml
index 9ac929b..58e7709 100644
--- a/packages/DocumentsUI/AndroidManifest.xml
+++ b/packages/DocumentsUI/AndroidManifest.xml
@@ -94,9 +94,8 @@
android:name=".OpenExternalDirectoryActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
<intent-filter>
- <action android:name="android.intent.action.OPEN_EXTERNAL_DIRECTORY" />
+ <action android:name="android.os.storage.action.OPEN_EXTERNAL_DIRECTORY" />
<category android:name="android.intent.category.DEFAULT" />
- <data android:scheme="file" />
</intent-filter>
</activity>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index d0bb7e0..ec7dde9 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -34,7 +34,6 @@
import android.content.ContentValues;
import android.content.Intent;
import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
@@ -74,8 +73,6 @@
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
- final Resources res = getResources();
-
if (mState.action == ACTION_CREATE) {
final String mimeType = getIntent().getType();
final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE);
@@ -134,9 +131,13 @@
}
if (state.action == ACTION_PICK_COPY_DESTINATION) {
+ // Indicates that a copy operation (or move) includes a directory.
+ // Why? Directory creation isn't supported by some roots (like Downloads).
+ // This allows us to restrict available roots to just those with support.
state.directoryCopy = intent.getBooleanExtra(
Shared.EXTRA_DIRECTORY_COPY, false);
- state.transferMode = intent.getIntExtra(FileOperationService.EXTRA_OPERATION,
+ state.copyOperationSubType = intent.getIntExtra(
+ FileOperationService.EXTRA_OPERATION,
FileOperationService.OPERATION_COPY);
}
}
@@ -156,6 +157,9 @@
if (external && mState.action == ACTION_GET_CONTENT) {
showDrawer = true;
}
+ if (mState.action == ACTION_PICK_COPY_DESTINATION) {
+ showDrawer = true;
+ }
if (showDrawer) {
mNavigator.revealRootsDrawer(true);
@@ -307,7 +311,7 @@
mState.action == ACTION_PICK_COPY_DESTINATION) {
final PickFragment pick = PickFragment.get(fm);
if (pick != null) {
- pick.setPickTarget(mState.action, mState.transferMode, cwd);
+ pick.setPickTarget(mState.action, mState.copyOperationSubType, cwd);
}
}
}
@@ -420,7 +424,7 @@
// Picking a copy destination is only used internally by us, so we
// don't need to extend permissions to the caller.
intent.putExtra(Shared.EXTRA_STACK, (Parcelable) mState.stack);
- intent.putExtra(FileOperationService.EXTRA_OPERATION, mState.transferMode);
+ intent.putExtra(FileOperationService.EXTRA_OPERATION, mState.copyOperationSubType);
} else {
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
diff --git a/packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java b/packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java
index d601550..025faea 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java
@@ -17,6 +17,8 @@
package com.android.documentsui;
import static android.os.Environment.isStandardDirectory;
+import static android.os.storage.StorageVolume.EXTRA_DIRECTORY_NAME;
+import static android.os.storage.StorageVolume.EXTRA_STORAGE_VOLUME;
import static com.android.documentsui.Shared.DEBUG;
import android.app.Activity;
@@ -35,9 +37,11 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Parcelable;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.storage.StorageManager;
+import android.os.storage.StorageVolume;
import android.os.storage.VolumeInfo;
import android.provider.DocumentsContract;
import android.text.TextUtils;
@@ -63,16 +67,31 @@
super.onCreate(savedInstanceState);
final Intent intent = getIntent();
- if (intent == null || intent.getData() == null) {
- Log.d(TAG, "missing intent or intent data: " + intent);
+ if (intent == null) {
+ if (DEBUG) Log.d(TAG, "missing intent");
+ setResult(RESULT_CANCELED);
+ finish();
+ return;
+ }
+ final Parcelable storageVolume = intent.getParcelableExtra(EXTRA_STORAGE_VOLUME);
+ if (!(storageVolume instanceof StorageVolume)) {
+ if (DEBUG)
+ Log.d(TAG, "extra " + EXTRA_STORAGE_VOLUME + " is not a StorageVolume: "
+ + storageVolume);
+ setResult(RESULT_CANCELED);
+ finish();
+ return;
+ }
+ final String directoryName = intent.getStringExtra(EXTRA_DIRECTORY_NAME);
+ if (directoryName == null) {
+ if (DEBUG) Log.d(TAG, "missing extra " + EXTRA_DIRECTORY_NAME + " on " + intent);
setResult(RESULT_CANCELED);
finish();
return;
}
- final String path = intent.getData().getPath();
final int userId = UserHandle.myUserId();
- if (!showFragment(this, userId, path)) {
+ if (!showFragment(this, userId, (StorageVolume) storageVolume, directoryName)) {
setResult(RESULT_CANCELED);
finish();
return;
@@ -80,20 +99,20 @@
}
/**
- * Validates the given {@code path} and display the appropriate dialog asking the user to grant
- * access to it.
+ * Validates the given path (volume + directory) and display the appropriate dialog asking the
+ * user to grant access to it.
*/
- static boolean showFragment(Activity activity, int userId, String path) {
- Log.d(TAG, "showFragment() for path " + path + " and user " + userId);
- if (path == null) {
- Log.e(TAG, "INTERNAL ERROR: showFragment() with null path");
- return false;
- }
+ private static boolean showFragment(Activity activity, int userId, StorageVolume storageVolume,
+ String directoryName) {
+ if (DEBUG)
+ Log.d(TAG, "showFragment() for volume " + storageVolume.dump() + ", directory "
+ + directoryName + ", and user " + userId);
File file;
try {
- file = new File(new File(path).getCanonicalPath());
+ file = new File(storageVolume.getPathFile(), directoryName).getCanonicalFile();
} catch (IOException e) {
- Log.e(TAG, "Could not get canonical file from " + path);
+ Log.e(TAG, "Could not get canonical file for volume " + storageVolume.dump()
+ + " and directory " + directoryName);
return false;
}
final StorageManager sm =
@@ -104,7 +123,9 @@
// Verify directory is valid.
if (TextUtils.isEmpty(directory) || !isStandardDirectory(directory)) {
- Log.d(TAG, "Directory '" + directory + "' is not standard (full path: '" + path + "')");
+ if (DEBUG)
+ Log.d(TAG, "Directory '" + directory + "' is not standard (full path: '"
+ + file.getAbsolutePath() + "')");
return false;
}
@@ -123,7 +144,7 @@
}
}
if (volumeLabel == null) {
- Log.e(TAG, "Could not get volume for " + path);
+ Log.e(TAG, "Could not get volume for " + file);
return false;
}
@@ -165,13 +186,13 @@
final File userPath = volume.getPathForUser(userId);
final String path = userPath == null ? null : volume.getPathForUser(userId).getPath();
final boolean isVisible = volume.isVisibleForWrite(userId);
- if (DEBUG) {
+ if (DEBUG)
Log.d(TAG, "Volume: " + volume + " userId: " + userId + " root: " + root
+ " volumePath: " + volume.getPath().getPath()
+ " pathForUser: " + path
+ " internalPathForUser: " + volume.getInternalPath()
+ " isVisible: " + isVisible);
- }
+
return volume.isVisibleForWrite(userId) && root.equals(path);
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
index bbf4682..287c904 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
@@ -16,6 +16,10 @@
package com.android.documentsui;
+import static com.android.documentsui.services.FileOperationService.OPERATION_COPY;
+import static com.android.documentsui.services.FileOperationService.OPERATION_MOVE;
+import static com.android.internal.util.Preconditions.checkArgument;
+
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
@@ -27,6 +31,7 @@
import android.widget.Button;
import com.android.documentsui.model.DocumentInfo;
+import com.android.documentsui.services.FileOperationService.OpType;
/**
* Display pick confirmation bar, usually for selecting a directory.
@@ -35,7 +40,7 @@
public static final String TAG = "PickFragment";
private int mAction;
- private int mTransferMode;
+ private @OpType int mOperationType;
private DocumentInfo mPickTarget;
private View mContainer;
private Button mPick;
@@ -92,9 +97,10 @@
/**
* @param action Which action defined in State is the picker shown for.
*/
- public void setPickTarget(int action, int transferMode, DocumentInfo pickTarget) {
+ public void setPickTarget(int action, @OpType int operationType, DocumentInfo pickTarget) {
+ checkArgument(operationType == OPERATION_COPY || operationType == OPERATION_MOVE);
mAction = action;
- mTransferMode = transferMode;
+ mOperationType = operationType;
mPickTarget = pickTarget;
if (mContainer != null) {
updateView();
@@ -111,7 +117,8 @@
mCancel.setVisibility(View.GONE);
break;
case State.ACTION_PICK_COPY_DESTINATION:
- mPick.setText(R.string.button_copy);
+ mPick.setText(mOperationType == OPERATION_MOVE
+ ? R.string.button_move : R.string.button_copy);
mCancel.setVisibility(View.VISIBLE);
break;
default:
diff --git a/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java b/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java
index c34cec0..a77a9b3 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java
@@ -33,16 +33,19 @@
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Range;
import com.android.documentsui.dirlist.Model;
import com.android.documentsui.model.DocumentInfo;
+import java.util.ArrayList;
import java.util.List;
/**
* Provides support for gather a list of quick-viewable files into a quick view intent.
*/
final class QuickViewIntentBuilder {
+ private static final int MAX_CLIP_ITEMS = 1000;
private final DocumentInfo mDocument;
private final Model mModel;
@@ -50,9 +53,6 @@
private final PackageManager mPkgManager;
private final Resources mResources;
- private ClipData mClipData;
- private int mDocumentLocation;
-
public QuickViewIntentBuilder(
PackageManager pkgManager,
Resources resources,
@@ -80,12 +80,28 @@
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setPackage(trustedPkg);
if (hasRegisteredHandler(intent)) {
- List<String> siblingIds = mModel.getModelIds();
- for (int i = 0; i < siblingIds.size(); i++) {
- onNextItem(i, siblingIds);
+ final ArrayList<Uri> uris = new ArrayList<Uri>();
+ final int documentLocation = collectViewableUris(uris);
+ final Range<Integer> range = computeSiblingsRange(uris, documentLocation);
+
+ ClipData clipData = null;
+ ClipData.Item item;
+ Uri uri;
+ for (int i = range.getLower(); i <= range.getUpper(); i++) {
+ uri = uris.get(i);
+ item = new ClipData.Item(uri);
+ if (DEBUG) Log.d(TAG, "Including file: " + uri);
+ if (clipData == null) {
+ clipData = new ClipData(
+ "URIs", new String[] { ClipDescription.MIMETYPE_TEXT_URILIST },
+ item);
+ } else {
+ clipData.addItem(item);
+ }
}
- intent.putExtra(Intent.EXTRA_INDEX, mDocumentLocation);
- intent.setClipData(mClipData);
+
+ intent.putExtra(Intent.EXTRA_INDEX, documentLocation);
+ intent.setClipData(clipData);
return intent;
} else {
@@ -96,39 +112,63 @@
return null;
}
+ private int collectViewableUris(ArrayList<Uri> uris) {
+ final List<String> siblingIds = mModel.getModelIds();
+ uris.ensureCapacity(siblingIds.size());
+
+ int documentLocation = 0;
+ Cursor cursor;
+ String mimeType;
+ String id;
+ String authority;
+ Uri uri;
+
+ // Cursor's are not guaranteed to be immutable. Hence, traverse it only once.
+ for (int i = 0; i < siblingIds.size(); i++) {
+ cursor = mModel.getItem(siblingIds.get(i));
+
+ mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
+ if (Document.MIME_TYPE_DIR.equals(mimeType)) {
+ continue;
+ }
+
+ id = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
+ authority = getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY);
+ uri = DocumentsContract.buildDocumentUri(authority, id);
+
+ if (id.equals(mDocument.documentId)) {
+ if (DEBUG) Log.d(TAG, "Found starting point for QV. " + i);
+ documentLocation = i;
+ }
+
+ uris.add(uri);
+ }
+
+ return documentLocation;
+ }
+
+ private static Range<Integer> computeSiblingsRange(List<Uri> uris, int documentLocation) {
+ // Restrict number of siblings to avoid hitting the IPC limit.
+ // TODO: Remove this restriction once ClipData can hold an arbitrary number of
+ // items.
+ int firstSibling;
+ int lastSibling;
+ if (documentLocation < uris.size() / 2) {
+ firstSibling = Math.max(0, documentLocation - MAX_CLIP_ITEMS / 2);
+ lastSibling = Math.min(uris.size() - 1, firstSibling + MAX_CLIP_ITEMS - 1);
+ } else {
+ lastSibling = Math.min(uris.size() - 1, documentLocation + MAX_CLIP_ITEMS / 2);
+ firstSibling = Math.max(0, lastSibling - MAX_CLIP_ITEMS + 1);
+ }
+
+ if (DEBUG) Log.d(TAG, "Copmuted siblings from index: " + firstSibling
+ + " to: " + lastSibling);
+
+ return new Range(firstSibling, lastSibling);
+ }
+
private boolean hasRegisteredHandler(Intent intent) {
// Try to resolve the intent. If a matching app isn't installed, it won't resolve.
return intent.resolveActivity(mPkgManager) != null;
}
-
- private void onNextItem(int index, List<String> siblingIds) {
- final Cursor cursor = mModel.getItem(siblingIds.get(index));
-
- if (cursor == null) {
- return;
- }
-
- String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
- if (Document.MIME_TYPE_DIR.equals(mimeType)) {
- return;
- }
-
- String id = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
- String authority = getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY);
- Uri uri = DocumentsContract.buildDocumentUri(authority, id);
- if (DEBUG) Log.d(TAG, "Including file[" + id + "] @ " + uri);
-
- if (id.equals(mDocument.documentId)) {
- if (DEBUG) Log.d(TAG, "Found starting point for QV. " + index);
- mDocumentLocation = index;
- }
-
- ClipData.Item item = new ClipData.Item(uri);
- if (mClipData == null) {
- mClipData = new ClipData(
- "URIs", new String[]{ClipDescription.MIMETYPE_TEXT_URILIST}, item);
- } else {
- mClipData.addItem(item);
- }
- }
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
index 29a72b8..fd96391 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
@@ -66,7 +66,7 @@
private final ContentObserver mObserver;
private OnCacheUpdateListener mCacheUpdateListener;
- private final RootInfo mRecentsRoot = new RootInfo();
+ private final RootInfo mRecentsRoot;
private final Object mLock = new Object();
private final CountDownLatch mFirstLoad = new CountDownLatch(1);
@@ -82,6 +82,18 @@
public RootsCache(Context context) {
mContext = context;
mObserver = new RootsChangedObserver();
+
+ // Create a new anonymous "Recents" RootInfo. It's a faker.
+ mRecentsRoot = new RootInfo() {{
+ // Special root for recents
+ authority = null;
+ rootId = null;
+ derivedIcon = R.drawable.ic_root_recent;
+ derivedType = RootInfo.TYPE_RECENTS;
+ flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_IS_CHILD;
+ title = mContext.getString(R.string.root_recent);
+ availableBytes = -1;
+ }};
}
private class RootsChangedObserver extends ContentObserver {
@@ -104,16 +116,6 @@
* Gather roots from all known storage providers.
*/
public void updateAsync() {
- // Special root for recents
- mRecentsRoot.authority = null;
- mRecentsRoot.rootId = null;
- mRecentsRoot.derivedIcon = R.drawable.ic_root_recent;
- mRecentsRoot.derivedType = RootInfo.TYPE_RECENTS;
- mRecentsRoot.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_CREATE
- | Root.FLAG_SUPPORTS_IS_CHILD;
- mRecentsRoot.title = mContext.getString(R.string.root_recent);
- mRecentsRoot.availableBytes = -1;
-
new UpdateTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@@ -360,7 +362,7 @@
}
public boolean isRecentsRoot(RootInfo root) {
- return mRecentsRoot == root;
+ return mRecentsRoot.equals(root);
}
public Collection<RootInfo> getRootsBlocking() {
@@ -400,27 +402,22 @@
static List<RootInfo> getMatchingRoots(Collection<RootInfo> roots, State state) {
final List<RootInfo> matching = new ArrayList<>();
for (RootInfo root : roots) {
- final boolean supportsCreate = (root.flags & Root.FLAG_SUPPORTS_CREATE) != 0;
- final boolean supportsIsChild = (root.flags & Root.FLAG_SUPPORTS_IS_CHILD) != 0;
- final boolean advanced = (root.flags & Root.FLAG_ADVANCED) != 0;
- final boolean localOnly = (root.flags & Root.FLAG_LOCAL_ONLY) != 0;
- final boolean empty = (root.flags & Root.FLAG_EMPTY) != 0;
-
// Exclude read-only devices when creating
- if (state.action == State.ACTION_CREATE && !supportsCreate) continue;
- if (state.action == State.ACTION_PICK_COPY_DESTINATION && !supportsCreate) continue;
+ if (state.action == State.ACTION_CREATE && !root.supportsCreate()) continue;
+ if (state.action == State.ACTION_PICK_COPY_DESTINATION
+ && !root.supportsCreate()) continue;
// Exclude roots that don't support directory picking
- if (state.action == State.ACTION_OPEN_TREE && !supportsIsChild) continue;
+ if (state.action == State.ACTION_OPEN_TREE && !root.supportsChildren()) continue;
// Exclude advanced devices when not requested
- if (!state.showAdvanced && advanced) continue;
+ if (!state.showAdvanced && root.isAdvanced()) continue;
// Exclude non-local devices when local only
- if (state.localOnly && !localOnly) continue;
+ if (state.localOnly && !root.isLocalOnly()) continue;
// Exclude downloads roots that don't support directory creation
// TODO: Add flag to check the root supports directory creation or not.
- if (state.directoryCopy && root.isDownloads()) continue;
+ if (state.directoryCopy && !root.supportsChildren()) continue;
// Only show empty roots when creating, or in browse mode.
- if (empty && (state.action == State.ACTION_OPEN
+ if (root.isEmpty() && (state.action == State.ACTION_OPEN
|| state.action == State.ACTION_GET_CONTENT)) {
if (DEBUG) Log.i(TAG, "Skipping empty root: " + root);
continue;
@@ -436,10 +433,13 @@
// Exclude roots from the calling package.
if (state.excludedAuthorities.contains(root.authority)) {
- if (DEBUG) Log.d(TAG, "Excluding root " + root.authority + " from calling package.");
+ if (DEBUG) Log.d(
+ TAG, "Excluding root " + root.authority + " from calling package.");
continue;
}
+ if (DEBUG) Log.d(
+ TAG, "Including root " + root + " in roots list.");
matching.add(root);
}
return matching;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
index 53f8297..f908eeb 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
@@ -30,6 +30,7 @@
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
+import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.text.format.Formatter;
import android.util.Log;
@@ -86,7 +87,6 @@
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- final Context context = inflater.getContext();
final View view = inflater.inflate(R.layout.fragment_roots, container, false);
mList = (ListView) view.findViewById(R.id.roots_list);
@@ -112,11 +112,13 @@
@Override
public void onLoadFinished(
Loader<Collection<RootInfo>> loader, Collection<RootInfo> result) {
- if (!isAdded()) return;
+ if (!isAdded()) {
+ return;
+ }
- final Intent includeApps = getArguments().getParcelable(EXTRA_INCLUDE_APPS);
+ Intent handlerAppIntent = getArguments().getParcelable(EXTRA_INCLUDE_APPS);
- mAdapter = new RootsAdapter(context, result, includeApps);
+ mAdapter = new RootsAdapter(context, result, handlerAppIntent);
mList.setAdapter(mAdapter);
onCurrentRootChanged();
@@ -151,7 +153,9 @@
}
public void onCurrentRootChanged() {
- if (mAdapter == null) return;
+ if (mAdapter == null) {
+ return;
+ }
final RootInfo root = ((BaseActivity) getActivity()).getCurrentRoot();
for (int i = 0; i < mAdapter.getCount(); i++) {
@@ -300,7 +304,13 @@
}
private static class RootsAdapter extends ArrayAdapter<Item> {
- public RootsAdapter(Context context, Collection<RootInfo> roots, Intent includeApps) {
+
+ /**
+ * @param handlerAppIntent When not null, apps capable of handling the original
+ * intent will be included in list of roots (in special section at bottom).
+ */
+ public RootsAdapter(
+ Context context, Collection<RootInfo> roots, @Nullable Intent handlerAppIntent) {
super(context, 0);
final List<RootItem> libraries = new ArrayList<>();
@@ -322,27 +332,39 @@
Collections.sort(others, comp);
addAll(libraries);
- add(new SpacerItem());
+ // Only add the spacer if it is actually separating something.
+ if (!libraries.isEmpty() && !others.isEmpty()) {
+ add(new SpacerItem());
+ }
addAll(others);
- if (includeApps != null) {
- final PackageManager pm = context.getPackageManager();
- final List<ResolveInfo> infos = pm.queryIntentActivities(
- includeApps, PackageManager.MATCH_DEFAULT_ONLY);
+ // Include apps that can handle this intent too.
+ if (handlerAppIntent != null) {
+ includeHandlerApps(context, handlerAppIntent);
+ }
+ }
- final List<AppItem> apps = new ArrayList<>();
+ /**
+ * Adds apps capable of handling the original intent will be included
+ * in list of roots (in special section at bottom).
+ */
+ private void includeHandlerApps(Context context, Intent handlerAppIntent) {
+ final PackageManager pm = context.getPackageManager();
+ final List<ResolveInfo> infos = pm.queryIntentActivities(
+ handlerAppIntent, PackageManager.MATCH_DEFAULT_ONLY);
- // Omit ourselves from the list
- for (ResolveInfo info : infos) {
- if (!context.getPackageName().equals(info.activityInfo.packageName)) {
- apps.add(new AppItem(info));
- }
+ final List<AppItem> apps = new ArrayList<>();
+
+ // Omit ourselves from the list
+ for (ResolveInfo info : infos) {
+ if (!context.getPackageName().equals(info.activityInfo.packageName)) {
+ apps.add(new AppItem(info));
}
+ }
- if (apps.size() > 0) {
- add(new SpacerItem());
- addAll(apps);
- }
+ if (apps.size() > 0) {
+ add(new SpacerItem());
+ addAll(apps);
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsLoader.java b/packages/DocumentsUI/src/com/android/documentsui/RootsLoader.java
index c81377a..5c4ccdd 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsLoader.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsLoader.java
@@ -50,7 +50,7 @@
if (isReset()) {
return;
}
- Collection<RootInfo> oldResult = mResult;
+
mResult = result;
if (isStarted()) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/State.java b/packages/DocumentsUI/src/com/android/documentsui/State.java
index 7dca8a7..81a0635 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/State.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/State.java
@@ -30,6 +30,7 @@
import com.android.documentsui.model.DocumentStack;
import com.android.documentsui.model.DurableUtils;
import com.android.documentsui.model.RootInfo;
+import com.android.documentsui.services.FileOperationService.OpType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -83,10 +84,18 @@
public boolean forceAdvanced;
public boolean showAdvanced;
public boolean restored;
+
+ // Indicates that a copy operation (or move) includes a directory.
+ // Why? Directory creation isn't supported by some roots (like Downloads).
+ // This allows us to restrict available roots to just those with support.
public boolean directoryCopy;
public boolean openableOnly;
- /** Transfer mode for file copy/move operations. */
- public int transferMode;
+
+ /**
+ * This is basically a sub-type for the copy operation. It can be either COPY or MOVE.
+ * The only legal values are: OPERATION_COPY, OPERATION_MOVE.
+ */
+ public @OpType int copyOperationSubType;
/** Current user navigation stack; empty implies recents. */
public DocumentStack stack = new DocumentStack();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 4233b36..236fa94 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -101,6 +101,7 @@
import com.android.documentsui.services.FileOperationService;
import com.android.documentsui.services.FileOperationService.OpType;
import com.android.documentsui.services.FileOperations;
+
import com.google.common.collect.Lists;
import java.lang.annotation.Retention;
@@ -130,6 +131,11 @@
public static final int ANIM_LEAVE = 3;
public static final int ANIM_ENTER = 4;
+ @IntDef(flag = true, value = {
+ REQUEST_COPY_DESTINATION
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RequestCode {}
public static final int REQUEST_COPY_DESTINATION = 1;
static final boolean DEBUG_ENABLE_DND = true;
@@ -193,11 +199,6 @@
mRecView.setItemAnimator(new DirectoryItemAnimator(getActivity()));
- // Make the RecyclerView unfocusable. This is needed in order for the focus search code in
- // FocusManager to work correctly. Setting android:focusable=false in the layout xml doesn't
- // work, for some reason.
- mRecView.setFocusable(false);
-
// TODO: Add a divider between views (which might use RecyclerView.ItemDecoration).
if (DEBUG_ENABLE_DND) {
setupDragAndDropOnDirectoryView(mRecView);
@@ -377,19 +378,24 @@
}
@Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- // There's only one request code right now. Replace this with a switch statement or
- // something more scalable when more codes are added.
- if (requestCode != REQUEST_COPY_DESTINATION) {
- return;
+ public void onActivityResult(@RequestCode int requestCode, int resultCode, Intent data) {
+ switch(requestCode) {
+ case REQUEST_COPY_DESTINATION:
+ handleCopyResult(resultCode, data);
+ break;
+ default:
+ throw new UnsupportedOperationException("Unknown request code: " + requestCode);
}
+ }
+
+ private void handleCopyResult(int resultCode, Intent data) {
if (resultCode == Activity.RESULT_CANCELED || data == null) {
// User pressed the back button or otherwise cancelled the destination pick. Don't
// proceed with the copy.
return;
}
- int operationType = data.getIntExtra(
+ @OpType int operationType = data.getIntExtra(
FileOperationService.EXTRA_OPERATION,
FileOperationService.OPERATION_COPY);
@@ -536,7 +542,11 @@
@Override
public void onItemStateChanged(String modelId, boolean selected) {
final Cursor cursor = mModel.getItem(modelId);
- checkNotNull(cursor, "Cursor cannot be null.");
+ if (cursor == null) {
+ Log.e(TAG, "Model returned null cursor for document: " + modelId
+ + ". Ignoring state changed event.");
+ return;
+ }
// TODO: Should this be happening in onSelectionChanged? Technically this callback is
// triggered on "silent" selection updates (i.e. we might be reacting to unfinalized
@@ -804,25 +814,43 @@
getActivity(),
DocumentsActivity.class);
+ // Set an appropriate title on the drawer when it is shown in the picker.
+ // Coupled with the fact that we auto-open the drawer for copy/move operations
+ // it should basically be the thing people see first.
+ int drawerTitleId = mode == FileOperationService.OPERATION_MOVE
+ ? R.string.menu_move : R.string.menu_copy;
+ intent.putExtra(DocumentsContract.EXTRA_PROMPT, getResources().getString(drawerTitleId));
+
new GetDocumentsTask() {
@Override
void onDocumentsReady(List<DocumentInfo> docs) {
+ // TODO: Can this move to Fragment bundle state?
getDisplayState().selectedDocumentsForCopy = docs;
- boolean directoryCopy = false;
- for (DocumentInfo info : docs) {
- if (Document.MIME_TYPE_DIR.equals(info.mimeType)) {
- directoryCopy = true;
- break;
- }
- }
- intent.putExtra(Shared.EXTRA_DIRECTORY_COPY, directoryCopy);
+ // Determine if there is a directory in the set of documents
+ // to be copied? Why? Directory creation isn't supported by some roots
+ // (like Downloads). This informs DocumentsActivity (the "picker")
+ // to restrict available roots to just those with support.
+ intent.putExtra(Shared.EXTRA_DIRECTORY_COPY, hasDirectory(docs));
intent.putExtra(FileOperationService.EXTRA_OPERATION, mode);
+
+ // This just identifies the type of request...we'll check it
+ // when we reveive a response.
startActivityForResult(intent, REQUEST_COPY_DESTINATION);
}
+
}.execute(selected);
}
+ private static boolean hasDirectory(List<DocumentInfo> docs) {
+ for (DocumentInfo info : docs) {
+ if (Document.MIME_TYPE_DIR.equals(info.mimeType)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private void renameDocuments(Selection selected) {
// Batch renaming not supported
// Rename option is only available in menu when 1 document selected
@@ -957,7 +985,8 @@
}
private void copyDocuments(final List<DocumentInfo> docs, final DocumentInfo destination) {
- if (!canCopy(docs, destination)) {
+ BaseActivity activity = (BaseActivity) getActivity();
+ if (!canCopy(docs, activity.getCurrentRoot(), destination)) {
Snackbars.makeSnackbar(
getActivity(),
R.string.clipboard_files_cannot_paste,
@@ -1037,13 +1066,13 @@
*
* @return true if the list of files can be copied to destination.
*/
- boolean canCopy(List<DocumentInfo> files, DocumentInfo dest) {
- BaseActivity activity = (BaseActivity) getActivity();
+ private boolean canCopy(List<DocumentInfo> files, RootInfo root, DocumentInfo dest) {
+ if (dest == null || !dest.isDirectory() || !dest.isCreateSupported()) {
+ return false;
+ }
- final RootInfo root = activity.getCurrentRoot();
-
- // Can't copy folders to Downloads.
- if (root.isDownloads()) {
+ // Can't copy folders to roots that don't support children.
+ if (!root.supportsChildren()) {
for (DocumentInfo docs : files) {
if (docs.isDirectory()) {
return false;
@@ -1051,7 +1080,7 @@
}
}
- return dest != null && dest.isDirectory() && dest.isCreateSupported();
+ return true;
}
public void selectAllFiles() {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java
index 93ec842..e90a447 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java
@@ -158,7 +158,14 @@
}
if (searchDir != -1) {
+ // Focus search behaves badly if the parent RecyclerView is focused. However, focusable
+ // shouldn't be unset on RecyclerView, otherwise focus isn't properly restored after
+ // events that cause a UI rebuild (like rotating the device). Compromise: turn focusable
+ // off while performing the focus search.
+ // TODO: Revisit this when RV focus issues are resolved.
+ mView.setFocusable(false);
View targetView = view.focusSearch(searchDir);
+ mView.setFocusable(true);
// TargetView can be null, for example, if the user pressed <down> at the bottom
// of the list.
if (targetView != null) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
index 4b5499a..1c696ad 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
@@ -39,6 +39,7 @@
import java.io.IOException;
import java.net.ProtocolException;
import java.text.Collator;
+import java.util.Objects;
/**
* Representation of a {@link Document}.
@@ -263,16 +264,23 @@
return derivedUri.hashCode() + mimeType.hashCode();
}
- public boolean equals(Object other) {
- if (this == other) {
- return true;
- } else if (!(other instanceof DocumentInfo)) {
+ public boolean equals(Object o) {
+ if (o == null) {
return false;
}
- DocumentInfo that = (DocumentInfo) other;
- // Uri + mime type should be totally unique.
- return derivedUri.equals(that.derivedUri) && mimeType.equals(that.mimeType);
+ if (this == o) {
+ return true;
+ }
+
+ if (o instanceof DocumentInfo) {
+ DocumentInfo other = (DocumentInfo) o;
+ // Uri + mime type should be totally unique.
+ return Objects.equals(derivedUri, other.derivedUri)
+ && Objects.equals(mimeType, other.mimeType);
+ }
+
+ return false;
}
public static String getCursorString(Cursor cursor, String columnName) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
index ffa8f59..8cbbb6c 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
@@ -249,6 +249,26 @@
return (flags & Root.FLAG_HAS_SETTINGS) != 0;
}
+ public boolean supportsChildren() {
+ return (flags & Root.FLAG_SUPPORTS_IS_CHILD) != 0;
+ }
+
+ public boolean supportsCreate() {
+ return (flags & Root.FLAG_SUPPORTS_CREATE) != 0;
+ }
+
+ public boolean isAdvanced() {
+ return (flags & Root.FLAG_ADVANCED) != 0;
+ }
+
+ public boolean isLocalOnly() {
+ return (flags & Root.FLAG_LOCAL_ONLY) != 0;
+ }
+
+ public boolean isEmpty() {
+ return (flags & Root.FLAG_EMPTY) != 0;
+ }
+
public Drawable loadIcon(Context context) {
if (derivedIcon != 0) {
return context.getDrawable(derivedIcon);
@@ -276,12 +296,21 @@
@Override
public boolean equals(Object o) {
- if (o instanceof RootInfo) {
- final RootInfo root = (RootInfo) o;
- return Objects.equals(authority, root.authority) && Objects.equals(rootId, root.rootId);
- } else {
+ if (o == null) {
return false;
}
+
+ if (this == o) {
+ return true;
+ }
+
+ if (o instanceof RootInfo) {
+ RootInfo other = (RootInfo) o;
+ return Objects.equals(authority, other.authority)
+ && Objects.equals(rootId, other.rootId);
+ }
+
+ return false;
}
@Override
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/FileOperationService.java b/packages/DocumentsUI/src/com/android/documentsui/services/FileOperationService.java
index 3a025c2..05a3f11 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/FileOperationService.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/FileOperationService.java
@@ -71,11 +71,6 @@
// such case, this needs to be replaced with pairs of parent and child.
public static final String EXTRA_SRC_PARENT = "com.android.documentsui.SRC_PARENT";
- public static final int OPERATION_UNKNOWN = -1;
- public static final int OPERATION_COPY = 1;
- public static final int OPERATION_MOVE = 2;
- public static final int OPERATION_DELETE = 3;
-
@IntDef(flag = true, value = {
OPERATION_UNKNOWN,
OPERATION_COPY,
@@ -84,6 +79,10 @@
})
@Retention(RetentionPolicy.SOURCE)
public @interface OpType {}
+ public static final int OPERATION_UNKNOWN = -1;
+ public static final int OPERATION_COPY = 1;
+ public static final int OPERATION_MOVE = 2;
+ public static final int OPERATION_DELETE = 3;
// TODO: Move it to a shared file when more operations are implemented.
public static final int FAILURE_COPY = 1;
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
index 609dc0c..95515db 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
@@ -52,7 +52,7 @@
"Videos",
"Audio",
"Downloads",
- "Home",
+ "Documents",
ROOT_0_ID,
ROOT_1_ID);
}
@@ -64,11 +64,11 @@
bot.assertHasDocuments("file0.log", "file1.png", "file2.csv");
}
- public void testLoadsHomeByDefault() throws Exception {
+ public void testLoadsHomeDirectoryByDefault() throws Exception {
initTestFiles();
device.waitForIdle();
- bot.assertWindowTitle("Home");
+ bot.assertWindowTitle("Documents");
}
public void testRootClickSetsWindowTitle() throws Exception {
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/StateTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/StateTest.java
index b74b985..f057850 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/StateTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/StateTest.java
@@ -23,18 +23,44 @@
@SmallTest
public class StateTest extends AndroidTestCase {
- public void testPushDocument() {
- final State state = new State();
- final DocumentInfo infoFirst = new DocumentInfo();
- infoFirst.displayName = "firstDirectory";
- final DocumentInfo infoSecond = new DocumentInfo();
- infoSecond.displayName = "secondDirectory";
- assertFalse(state.hasLocationChanged());
- state.pushDocument(infoFirst);
- state.pushDocument(infoSecond);
- assertTrue(state.hasLocationChanged());
- assertEquals("secondDirectory", state.stack.getFirst().displayName);
- state.popDocument();
- assertEquals("firstDirectory", state.stack.getFirst().displayName);
+
+ private static final DocumentInfo DIR_1;
+ private static final DocumentInfo DIR_2;
+
+ private State mState;
+
+ static {
+ DIR_1 = new DocumentInfo();
+ DIR_1.displayName = "firstDirectory";
+ DIR_2 = new DocumentInfo();
+ DIR_2.displayName = "secondDirectory";
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ mState = new State();
+ }
+
+ public void testInitialStateEmpty() {
+ assertFalse(mState.hasLocationChanged());
+ }
+
+ public void testPushDocument_ChangesLocation() {
+ mState.pushDocument(DIR_1);
+ mState.pushDocument(DIR_2);
+ assertTrue(mState.hasLocationChanged());
+ }
+
+ public void testPushDocument_ModifiesStack() {
+ mState.pushDocument(DIR_1);
+ mState.pushDocument(DIR_2);
+ assertEquals(DIR_2, mState.stack.getFirst());
+ }
+
+ public void testPopDocument_ModifiesStack() {
+ mState.pushDocument(DIR_1);
+ mState.pushDocument(DIR_2);
+ mState.popDocument();
+ assertEquals(DIR_1, mState.stack.getFirst());
}
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/model/DocumentInfoTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/model/DocumentInfoTest.java
index a6aba7b..2481dc3 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/model/DocumentInfoTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/model/DocumentInfoTest.java
@@ -22,30 +22,36 @@
@SmallTest
public class DocumentInfoTest extends AndroidTestCase {
+ private static final DocumentInfo TEST_DOC
+ = createDocInfo("authority.a", "doc.1", "text/plain");
+
public void testEquals() throws Exception {
- DocumentInfo doc = createDocInfo("authority.a", "doc.1", "text/plain");
- assertEquals(doc, doc);
+ assertEquals(TEST_DOC, TEST_DOC);
+ assertEquals(TEST_DOC, createDocInfo("authority.a", "doc.1", "text/plain"));
+ }
+
+ public void testEquals_HandlesNulls() throws Exception {
+ assertFalse(TEST_DOC.equals(null));
+ }
+
+ public void testEquals_HandlesNullFields() throws Exception {
+ assertFalse(TEST_DOC.equals(new DocumentInfo()));
+ assertFalse(new DocumentInfo().equals(TEST_DOC));
}
public void testNotEquals_differentAuthority() throws Exception {
- DocumentInfo docA = createDocInfo("authority.a", "doc.1", "text/plain");
- DocumentInfo docB = createDocInfo("authority.b", "doc.1", "text/plain");
- assertFalse(docA.equals(docB));
+ assertFalse(TEST_DOC.equals(createDocInfo("authority.b", "doc.1", "text/plain")));
}
public void testNotEquals_differentDocId() throws Exception {
- DocumentInfo docA = createDocInfo("authority.a", "doc.1", "text/plain");
- DocumentInfo docB = createDocInfo("authority.a", "doc.2", "text/plain");
- assertFalse(docA.equals(docB));
+ assertFalse(TEST_DOC.equals(createDocInfo("authority.a", "doc.2", "text/plain")));
}
public void testNotEquals_differentMimetype() throws Exception {
- DocumentInfo docA = createDocInfo("authority.a", "doc.1", "text/plain");
- DocumentInfo docB = createDocInfo("authority.a", "doc.1", "image/png");
- assertFalse(docA.equals(docB));
+ assertFalse(TEST_DOC.equals(createDocInfo("authority.a", "doc.1", "image/png")));
}
- private DocumentInfo createDocInfo(String authority, String docId, String mimeType) {
+ private static DocumentInfo createDocInfo(String authority, String docId, String mimeType) {
DocumentInfo doc = new DocumentInfo();
doc.authority = authority;
doc.documentId = docId;
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 96506512..7012eb2 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -626,12 +626,12 @@
<!-- UI debug setting: force all activites to be resizable for multiwindow [CHAR LIMIT=50] -->
<string name="force_resizable_activities">Force activities to be resizable</string>
<!-- UI debug setting: force allow on external summary [CHAR LIMIT=150] -->
- <string name="force_resizable_activities_summary">Makes all activities resizable for multi-window, regardless of manifest values.</string>
+ <string name="force_resizable_activities_summary">Make all activities resizable for multi-window, regardless of manifest values.</string>
<!-- UI debug setting: enable freeform window support [CHAR LIMIT=50] -->
<string name="enable_freeform_support">Enable freeform windows</string>
<!-- UI debug setting: enable freeform window support summary [CHAR LIMIT=150] -->
- <string name="enable_freeform_support_summary">Enables support for experimental freeform windows.</string>
+ <string name="enable_freeform_support_summary">Enable support for experimental freeform windows.</string>
<!-- Local (desktop) backup password menu title [CHAR LIMIT=25] -->
<string name="local_backup_password_title">Desktop backup password</string>
diff --git a/packages/SystemUI/res/layout-sw600dp/navigation_layout_rot90.xml b/packages/SystemUI/res/layout-sw600dp/navigation_layout_rot90.xml
index 6f98509..6f153c1 100644
--- a/packages/SystemUI/res/layout-sw600dp/navigation_layout_rot90.xml
+++ b/packages/SystemUI/res/layout-sw600dp/navigation_layout_rot90.xml
@@ -41,26 +41,6 @@
</FrameLayout>
- <FrameLayout
- android:id="@+id/lights_out"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <LinearLayout
- android:id="@+id/ends_group_lightsout"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="horizontal" />
-
- <LinearLayout
- android:id="@+id/center_group_lightsout"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="center"
- android:orientation="horizontal" />
-
- </FrameLayout>
-
<com.android.systemui.statusbar.policy.DeadZone
android:id="@+id/deadzone"
android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/layout/navigation_layout.xml b/packages/SystemUI/res/layout/navigation_layout.xml
index 142d13a..2bf4d9c 100644
--- a/packages/SystemUI/res/layout/navigation_layout.xml
+++ b/packages/SystemUI/res/layout/navigation_layout.xml
@@ -42,26 +42,6 @@
</FrameLayout>
- <FrameLayout
- android:id="@+id/lights_out"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <LinearLayout
- android:id="@+id/ends_group_lightsout"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="horizontal" />
-
- <LinearLayout
- android:id="@+id/center_group_lightsout"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="center"
- android:orientation="horizontal" />
-
- </FrameLayout>
-
<com.android.systemui.statusbar.policy.DeadZone
android:id="@+id/deadzone"
android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/layout/navigation_layout_rot90.xml b/packages/SystemUI/res/layout/navigation_layout_rot90.xml
index 3b7b369..7601efc 100644
--- a/packages/SystemUI/res/layout/navigation_layout_rot90.xml
+++ b/packages/SystemUI/res/layout/navigation_layout_rot90.xml
@@ -42,26 +42,6 @@
</FrameLayout>
- <FrameLayout
- android:id="@+id/lights_out"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <com.android.systemui.statusbar.phone.ReverseLinearLayout
- android:id="@+id/ends_group_lightsout"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical" />
-
- <com.android.systemui.statusbar.phone.ReverseLinearLayout
- android:id="@+id/center_group_lightsout"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="center"
- android:orientation="vertical" />
-
- </FrameLayout>
-
<com.android.systemui.statusbar.policy.DeadZone
android:id="@+id/deadzone"
android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/layout/qs_detail.xml b/packages/SystemUI/res/layout/qs_detail.xml
index bed8f1b..858f487 100644
--- a/packages/SystemUI/res/layout/qs_detail.xml
+++ b/packages/SystemUI/res/layout/qs_detail.xml
@@ -50,30 +50,6 @@
android:layout_height="0dp"
android:layout_weight="1" />
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingEnd="8dp"
- android:gravity="end">
+ <include layout="@layout/qs_detail_buttons" />
- <TextView
- android:id="@android:id/button2"
- style="@style/QSBorderlessButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginEnd="8dp"
- android:minWidth="132dp"
- android:textAppearance="@style/TextAppearance.QS.DetailButton"
- android:focusable="true" />
-
- <TextView
- android:id="@android:id/button1"
- style="@style/QSBorderlessButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:minWidth="88dp"
- android:textAppearance="@style/TextAppearance.QS.DetailButton"
- android:focusable="true"/>
-
- </LinearLayout>
</com.android.systemui.qs.QSDetail>
diff --git a/packages/SystemUI/res/layout/qs_detail_buttons.xml b/packages/SystemUI/res/layout/qs_detail_buttons.xml
new file mode 100644
index 0000000..03ed62b
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_detail_buttons.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingEnd="8dp"
+ android:gravity="end">
+
+ <TextView
+ android:id="@android:id/button2"
+ style="@style/QSBorderlessButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="8dp"
+ android:minWidth="132dp"
+ android:textAppearance="@style/TextAppearance.QS.DetailButton"
+ android:focusable="true" />
+
+ <TextView
+ android:id="@android:id/button1"
+ style="@style/QSBorderlessButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="88dp"
+ android:textAppearance="@style/TextAppearance.QS.DetailButton"
+ android:focusable="true"/>
+
+</LinearLayout>
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 84df0d6..8df2c280 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -32,21 +32,32 @@
>
<LinearLayout
- android:id="@+id/expanded_group"
android:layout_width="wrap_content"
- android:layout_height="match_parent"
+ android:layout_height="48dp"
android:gravity="center"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="horizontal"
android:layout_alignParentEnd="true"
- android:layout_marginTop="30dp"
- android:layout_marginEnd="16dp">
+ android:layout_marginTop="28dp"
+ android:layout_marginEnd="12dp">
+
+ <com.android.systemui.statusbar.phone.MultiUserSwitch android:id="@+id/multi_user_switch"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_alignParentEnd="true"
+ android:background="@drawable/ripple_drawable" >
+ <ImageView android:id="@+id/multi_user_avatar"
+ android:layout_width="@dimen/multi_user_avatar_expanded_size"
+ android:layout_height="@dimen/multi_user_avatar_expanded_size"
+ android:layout_gravity="center"
+ android:scaleType="centerInside"/>
+ </com.android.systemui.statusbar.phone.MultiUserSwitch>
<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
android:id="@+id/settings_button_container"
android:layout_width="48dp"
- android:layout_height="@dimen/status_bar_header_height"
+ android:layout_height="48dp"
android:clipChildren="false"
android:clipToPadding="false">
@@ -68,24 +79,12 @@
</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
- <com.android.systemui.statusbar.phone.MultiUserSwitch android:id="@+id/multi_user_switch"
+ <com.android.systemui.statusbar.phone.ExpandableIndicator
+ android:id="@+id/expand_indicator"
android:layout_width="48dp"
android:layout_height="48dp"
- android:layout_alignParentEnd="true"
- android:background="@drawable/ripple_drawable" >
- <ImageView android:id="@+id/multi_user_avatar"
- android:layout_width="@dimen/multi_user_avatar_expanded_size"
- android:layout_height="@dimen/multi_user_avatar_expanded_size"
- android:layout_gravity="center"
- android:scaleType="centerInside"/>
- </com.android.systemui.statusbar.phone.MultiUserSwitch>
+ android:padding="12dp" />
- <ImageView
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:padding="12dp"
- android:src="@drawable/ic_expand_less"
- android:tint="@android:color/white" />
</LinearLayout>
<TextView
@@ -104,44 +103,62 @@
android:gravity="center_vertical" />
<LinearLayout
- android:id="@+id/date_time_group"
+ android:id="@+id/date_time_alarm_group"
android:layout_width="wrap_content"
- android:layout_height="25dp"
+ android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
- android:orientation="horizontal">
-
- <include layout="@layout/split_clock_view"
+ android:layout_marginTop="4dp"
+ android:layout_marginStart="16dp"
+ android:gravity="start"
+ android:orientation="vertical">
+ <LinearLayout
+ android:id="@+id/date_time_group"
android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_marginStart="16dp"
- android:layout_marginTop="4dp"
- android:id="@+id/clock" />
+ android:layout_height="19dp"
+ android:orientation="horizontal">
- <com.android.systemui.statusbar.policy.DateView
- android:id="@+id/date"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_marginStart="6dp"
- android:layout_marginTop="4dp"
- android:drawableStart="@drawable/header_dot"
- android:drawablePadding="6dp"
- android:singleLine="true"
- android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
- android:textSize="@dimen/qs_time_collapsed_size"
- android:gravity="top"
- systemui:datePattern="@string/abbrev_wday_month_day_no_year_alarm" />
+ <include layout="@layout/split_clock_view"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:id="@+id/clock" />
+
+ <com.android.systemui.statusbar.policy.DateView
+ android:id="@+id/date"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="6dp"
+ android:drawableStart="@drawable/header_dot"
+ android:drawablePadding="6dp"
+ android:singleLine="true"
+ android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
+ android:textSize="@dimen/qs_time_collapsed_size"
+ android:gravity="top"
+ systemui:datePattern="@string/abbrev_wday_month_day_no_year_alarm" />
+
+ <com.android.systemui.statusbar.AlphaOptimizedButton
+ android:id="@+id/alarm_status_collapsed"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawablePadding="6dp"
+ android:drawableStart="@drawable/ic_access_alarms_small"
+ android:textColor="#64ffffff"
+ android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Date"
+ android:paddingStart="6dp"
+ android:gravity="top"
+ android:background="?android:attr/selectableItemBackground"
+ android:visibility="gone" />
+ </LinearLayout>
<com.android.systemui.statusbar.AlphaOptimizedButton
android:id="@+id/alarm_status"
android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_marginTop="4dp"
- android:drawablePadding="6dp"
+ android:layout_height="20dp"
+ android:paddingTop="3dp"
+ android:drawablePadding="8dp"
android:drawableStart="@drawable/ic_access_alarms_small"
android:textColor="#64ffffff"
android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Date"
- android:paddingStart="6dp"
android:gravity="top"
android:background="?android:attr/selectableItemBackground"
android:visibility="gone" />
@@ -152,7 +169,7 @@
android:background="#0000"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="25dp"
+ android:layout_marginTop="28dp"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:layout_alignParentEnd="true"
diff --git a/packages/SystemUI/res/layout/tuner_zen_mode_panel.xml b/packages/SystemUI/res/layout/tuner_zen_mode_panel.xml
new file mode 100644
index 0000000..efe63d7
--- /dev/null
+++ b/packages/SystemUI/res/layout/tuner_zen_mode_panel.xml
@@ -0,0 +1,48 @@
+<?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.
+-->
+<!-- extends LinearLayout -->
+<com.android.systemui.tuner.TunerZenModePanel
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/tuner_zen_mode_panel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:visibility="gone"
+ android:orientation="vertical" >
+
+ <View
+ android:id="@+id/zen_embedded_divider"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:layout_marginBottom="12dp"
+ android:layout_marginTop="8dp"
+ android:background="@color/qs_tile_divider" />
+
+ <include
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:layout_marginStart="16dp"
+ android:id="@+id/tuner_zen_switch"
+ layout="@layout/qs_detail_header" />
+
+ <include layout="@layout/zen_mode_panel" />
+
+ <include
+ android:id="@+id/tuner_zen_buttons"
+ layout="@layout/qs_detail_buttons" />
+
+</com.android.systemui.tuner.TunerZenModePanel>
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index 34796cd..e4effd4 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -46,7 +46,7 @@
<include layout="@layout/volume_zen_footer" />
<!-- Only shown from Tuner setting -->
- <include layout="@layout/zen_mode_panel" />
+ <include layout="@layout/tuner_zen_mode_panel" />
</LinearLayout>
</RelativeLayout>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 11c13e1..b8044ba 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -162,7 +162,11 @@
<dimen name="qs_tile_margin">16dp</dimen>
<dimen name="qs_quick_tile_size">48dp</dimen>
<dimen name="qs_quick_tile_padding">12dp</dimen>
- <dimen name="qs_date_anim_translation">44.5dp</dimen>
+ <dimen name="qs_date_anim_translation">36dp</dimen>
+ <dimen name="qs_date_alarm_anim_translation">26dp</dimen>
+ <dimen name="qs_date_collapsed_text_size">14sp</dimen>
+ <dimen name="qs_date_text_size">16sp</dimen>
+ <dimen name="qs_header_gear_translation">120dp</dimen>
<dimen name="qs_page_indicator_size">12dp</dimen>
<dimen name="qs_tile_icon_size">24dp</dimen>
<dimen name="qs_tile_text_size">12sp</dimen>
@@ -598,9 +602,6 @@
<dimen name="fab_elevation">12dp</dimen>
<dimen name="fab_press_translation_z">9dp</dimen>
- <!-- TODO: Remove this -->
- <dimen name="qs_header_neg_padding">-8dp</dimen>
-
<!-- How high we lift the divider when touching -->
<dimen name="docked_stack_divider_lift_elevation">4dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index ec04861..6135dc6 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -810,12 +810,6 @@
<!-- Interruption level: Alarms only. Optimized for narrow two-line display. [CHAR LIMIT=40] -->
<string name="interruption_level_alarms_twoline">Alarms\nonly</string>
- <!-- Interruption level: All interruptions. [CHAR LIMIT=40] -->
- <string name="interruption_level_all">All</string>
-
- <!-- Interruption level: All interruptions. Optimized for narrow two-line display. [CHAR LIMIT=40] -->
- <string name="interruption_level_all_twoline">All\n</string>
-
<!-- Indication on the keyguard that is shown when the device is charging. [CHAR LIMIT=40]-->
<string name="keyguard_indication_charging_time">Charging (<xliff:g id="charging_time_left" example="4 hours and 2 minutes">%s</xliff:g> until full)</string>
@@ -1182,7 +1176,7 @@
<string name="overview_disable_fast_toggle_via_button_desc">Disable launch timeout while paging</string>
<!-- Toggle to enable the gesture to enter split-screen by swiping up from the Overview button. [CHAR LIMIT=60]-->
- <string name="overview_nav_bar_gesture">Enable split-screen swipe-up accelerator</string>
+ <string name="overview_nav_bar_gesture">Enable split-screen swipe-up gesture</string>
<!-- Description for the toggle to enable the gesture to enter split-screen by swiping up from the Overview button. [CHAR LIMIT=NONE]-->
<string name="overview_nav_bar_gesture_desc">Enable gesture to enter split-screen by swiping up from the Overview button</string>
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 90d56f7..6029c23 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -758,7 +758,7 @@
mPendingLock = false;
}
}
- doKeyguardLaterLockedForChildProfiles();
+ doKeyguardForChildProfilesLocked();
KeyguardUpdateMonitor.getInstance(mContext).dispatchFinishedGoingToSleep(why);
}
@@ -781,8 +781,7 @@
long timeout;
- if ((mLockPatternUtils.isSeparateProfileChallengeEnabled(userId))
- || policyTimeout <= 0) {
+ if (policyTimeout <= 0) {
timeout = lockAfterTimeout;
} else {
// From DisplaySettings
@@ -815,23 +814,31 @@
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, when, sender);
if (DEBUG) Log.d(TAG, "setting alarm to turn off keyguard, seq = "
+ mDelayedShowingSequence);
- doKeyguardLaterLockedForChildProfiles();
+ doKeyguardLaterForChildProfilesLocked();
}
- private void doKeyguardLaterLockedForChildProfiles() {
+ private void doKeyguardLaterForChildProfilesLocked() {
UserManager um = UserManager.get(mContext);
List<UserInfo> profiles = um.getEnabledProfiles(UserHandle.myUserId());
- if (profiles.size() > 1) {
- for (UserInfo info : profiles) {
- if (mLockPatternUtils.isSeparateProfileChallengeEnabled(info.id)) {
- long userTimeout = getLockTimeout(info.id);
- long userWhen = SystemClock.elapsedRealtime() + userTimeout;
- Intent lockIntent = new Intent(DELAYED_LOCK_PROFILE_ACTION);
- lockIntent.putExtra(Intent.EXTRA_USER_ID, info.id);
- PendingIntent lockSender = PendingIntent.getBroadcast(
- mContext, 0, lockIntent, PendingIntent.FLAG_CANCEL_CURRENT);
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, userWhen, lockSender);
- }
+ for (UserInfo info : profiles) {
+ if (mLockPatternUtils.isSeparateProfileChallengeEnabled(info.id)) {
+ long userTimeout = getLockTimeout(info.id);
+ long userWhen = SystemClock.elapsedRealtime() + userTimeout;
+ Intent lockIntent = new Intent(DELAYED_LOCK_PROFILE_ACTION);
+ lockIntent.putExtra(Intent.EXTRA_USER_ID, info.id);
+ PendingIntent lockSender = PendingIntent.getBroadcast(
+ mContext, 0, lockIntent, PendingIntent.FLAG_CANCEL_CURRENT);
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, userWhen, lockSender);
+ }
+ }
+ }
+
+ private void doKeyguardForChildProfilesLocked() {
+ UserManager um = UserManager.get(mContext);
+ List<UserInfo> profiles = um.getEnabledProfiles(UserHandle.myUserId());
+ for (UserInfo info : profiles) {
+ if (mLockPatternUtils.isSeparateProfileChallengeEnabled(info.id)) {
+ lockProfile(info.id);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index f208470..d0f7e6e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -17,12 +17,10 @@
package com.android.systemui.qs;
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;
@@ -103,7 +101,7 @@
private static class HeaderTileLayout extends LinearLayout implements QSTileLayout {
- private final ImageView mDownArrow;
+ private final Space mEndSpacer;
public HeaderTileLayout(Context context) {
super(context);
@@ -112,16 +110,10 @@
setGravity(Gravity.CENTER_VERTICAL);
setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
- int padding =
- mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_padding);
- mDownArrow = new ImageView(context);
- mDownArrow.setImageResource(R.drawable.ic_expand_more);
- mDownArrow.setImageTintList(ColorStateList.valueOf(context.getResources().getColor(
- android.R.color.white, null)));
- mDownArrow.setLayoutParams(generateLayoutParams());
- mDownArrow.setPadding(padding, padding, padding, padding);
+ mEndSpacer = new Space(context);
+ mEndSpacer.setLayoutParams(generateLayoutParams());
updateDownArrowMargin();
- addView(mDownArrow);
+ addView(mEndSpacer);
setOrientation(LinearLayout.HORIZONTAL);
}
@@ -132,10 +124,10 @@
}
private void updateDownArrowMargin() {
- LayoutParams params = (LayoutParams) mDownArrow.getLayoutParams();
+ LayoutParams params = (LayoutParams) mEndSpacer.getLayoutParams();
params.setMarginStart(mContext.getResources().getDimensionPixelSize(
R.dimen.qs_expand_margin));
- mDownArrow.setLayoutParams(params);
+ mEndSpacer.setLayoutParams(params);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java
new file mode 100644
index 0000000..5e6b52b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java
@@ -0,0 +1,241 @@
+/*
+ * 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;
+
+import android.animation.Keyframe;
+import android.util.MathUtils;
+import android.util.Property;
+import android.view.animation.Interpolator;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper class, that handles similar properties as animators (delay, interpolators)
+ * but can have a float input as to the amount they should be in effect. This allows
+ * easier animation that tracks input.
+ *
+ * All "delays" and "times" are as fractions from 0-1.
+ */
+public class TouchAnimator {
+
+ private final Object[] mTargets;
+ private final Property[] mProperties;
+ private final KeyframeSet[] mKeyframeSets;
+ private final float mStartDelay;
+ private final float mEndDelay;
+ private final float mSpan;
+ private final Interpolator mInterpolator;
+ private final Listener mListener;
+ private float mLastT;
+
+ private TouchAnimator(Object[] targets, Property[] properties, KeyframeSet[] keyframeSets,
+ float startDelay, float endDelay, Interpolator interpolator, Listener listener) {
+ mTargets = targets;
+ mProperties = properties;
+ mKeyframeSets = keyframeSets;
+ mStartDelay = startDelay;
+ mEndDelay = endDelay;
+ mSpan = (1 - mEndDelay - mStartDelay);
+ mInterpolator = interpolator;
+ mListener = listener;
+ }
+
+ public void setPosition(float fraction) {
+ float t = MathUtils.constrain((fraction - mStartDelay) / mSpan, 0, 1);
+ if (mInterpolator != null) {
+ t = mInterpolator.getInterpolation(t);
+ }
+ if (mListener != null) {
+ if (mLastT == 0 || mLastT == 1) {
+ if (t != 0) {
+ mListener.onAnimationStarted();
+ }
+ } else if (t == 1) {
+ mListener.onAnimationAtEnd();
+ } else if (t == 0) {
+ mListener.onAnimationAtStart();
+ }
+ mLastT = t;
+ }
+ for (int i = 0; i < mTargets.length; i++) {
+ Object value = mKeyframeSets[i].getValue(t);
+ mProperties[i].set(mTargets[i], value);
+ }
+ }
+
+ public static class ListenerAdapter implements Listener {
+ @Override
+ public void onAnimationAtStart() { }
+
+ @Override
+ public void onAnimationAtEnd() { }
+
+ @Override
+ public void onAnimationStarted() { }
+ }
+
+ public interface Listener {
+ /**
+ * Called when the animator moves into a position of "0". Start and end delays are
+ * taken into account, so this position may cover a range of fractional inputs.
+ */
+ void onAnimationAtStart();
+
+ /**
+ * Called when the animator moves into a position of "0". Start and end delays are
+ * taken into account, so this position may cover a range of fractional inputs.
+ */
+ void onAnimationAtEnd();
+
+ /**
+ * Called when the animator moves out of the start or end position and is in a transient
+ * state.
+ */
+ void onAnimationStarted();
+ }
+
+ public static class Builder {
+ private List<Object> mTargets = new ArrayList<>();
+ private List<Property> mProperties = new ArrayList<>();
+ private List<KeyframeSet> mValues = new ArrayList<>();
+
+ private float mStartDelay;
+ private float mEndDelay;
+ private Interpolator mInterpolator;
+ private Listener mListener;
+
+ public Builder addFloat(Object target, String property, float... values) {
+ add(target, property, KeyframeSet.ofFloat(values));
+ return this;
+ }
+
+ public Builder addInt(Object target, String property, int... values) {
+ add(target, property, KeyframeSet.ofInt(values));
+ return this;
+ }
+
+ private void add(Object target, String property, KeyframeSet keyframeSet) {
+ mTargets.add(target);
+ // TODO: Optimize the properties here, to use those in View when possible.
+ mProperties.add(Property.of(target.getClass(), float.class, property));
+ mValues.add(keyframeSet);
+ }
+
+ public Builder setStartDelay(float startDelay) {
+ mStartDelay = startDelay;
+ return this;
+ }
+
+ public Builder setEndDelay(float endDelay) {
+ mEndDelay = endDelay;
+ return this;
+ }
+
+ public Builder setInterpolator(Interpolator intepolator) {
+ mInterpolator = intepolator;
+ return this;
+ }
+
+ public Builder setListener(Listener listener) {
+ mListener = listener;
+ return this;
+ }
+
+ public TouchAnimator build() {
+ return new TouchAnimator(mTargets.toArray(new Object[mTargets.size()]),
+ mProperties.toArray(new Property[mProperties.size()]),
+ mValues.toArray(new KeyframeSet[mValues.size()]),
+ mStartDelay, mEndDelay, mInterpolator, mListener);
+ }
+ }
+
+ private static abstract class KeyframeSet {
+
+ private final Keyframe[] mKeyframes;
+
+ public KeyframeSet(Keyframe[] keyframes) {
+ mKeyframes = keyframes;
+ }
+
+ Object getValue(float fraction) {
+ int i;
+ for (i = 1; i < mKeyframes.length && fraction > mKeyframes[i].getFraction(); i++) ;
+ Keyframe first = mKeyframes[i - 1];
+ Keyframe second = mKeyframes[i];
+ float amount = (fraction - first.getFraction())
+ / (second.getFraction() - first.getFraction());
+ return interpolate(first, second, amount);
+ }
+
+ protected abstract Object interpolate(Keyframe first, Keyframe second, float amount);
+
+ public static KeyframeSet ofInt(int... values) {
+ int numKeyframes = values.length;
+ Keyframe keyframes[] = new Keyframe[Math.max(numKeyframes, 2)];
+ if (numKeyframes == 1) {
+ keyframes[0] = Keyframe.ofInt(0f);
+ keyframes[1] = Keyframe.ofInt(1f, values[0]);
+ } else {
+ keyframes[0] = Keyframe.ofInt(0f, values[0]);
+ for (int i = 1; i < numKeyframes; ++i) {
+ keyframes[i] = Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);
+ }
+ }
+ return new IntKeyframeSet(keyframes);
+ }
+
+ public static KeyframeSet ofFloat(float... values) {
+ int numKeyframes = values.length;
+ Keyframe keyframes[] = new Keyframe[Math.max(numKeyframes, 2)];
+ if (numKeyframes == 1) {
+ keyframes[0] = Keyframe.ofFloat(0f);
+ keyframes[1] = Keyframe.ofFloat(1f, values[0]);
+ } else {
+ keyframes[0] = Keyframe.ofFloat(0f, values[0]);
+ for (int i = 1; i < numKeyframes; ++i) {
+ keyframes[i] = Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
+ }
+ }
+ return new FloatKeyframeSet(keyframes);
+ }
+ }
+
+ public static class FloatKeyframeSet extends KeyframeSet {
+ public FloatKeyframeSet(Keyframe[] keyframes) {
+ super(keyframes);
+ }
+
+ @Override
+ protected Object interpolate(Keyframe first, Keyframe second, float amount) {
+ float firstFloat = (float) first.getValue();
+ float secondFloat = (float) second.getValue();
+ return firstFloat + (secondFloat - firstFloat) * amount;
+ }
+ }
+
+ public static class IntKeyframeSet extends KeyframeSet {
+ public IntKeyframeSet(Keyframe[] keyframes) {
+ super(keyframes);
+ }
+
+ @Override
+ protected Object interpolate(Keyframe first, Keyframe second, float amount) {
+ int firstFloat = (int) first.getValue();
+ int secondFloat = (int) second.getValue();
+ return (int) (firstFloat + (secondFloat - firstFloat) * amount);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
index a58fa86..8a93c5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
@@ -23,6 +23,7 @@
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.support.v4.util.SimpleArrayMap;
+import android.util.SparseBooleanArray;
import android.view.View;
import android.widget.LinearLayout;
@@ -71,6 +72,7 @@
private int mCurrentFacetIndex;
private String mCurrentPackageName;
+ private SparseBooleanArray mFacetHasMultipleAppsCache = new SparseBooleanArray();
public CarNavigationBarController(Context context,
CarNavigationBarView navBar,
@@ -96,6 +98,21 @@
}
}
+ public void onPackageChange(String packageName) {
+ if (mFacetPackageMap.containsKey(packageName)) {
+ int index = mFacetPackageMap.get(packageName);
+ mFacetHasMultipleAppsCache.put(index, facetHasMultiplePackages(index));
+ // No need to check categories because we've already refreshed the cache.
+ return;
+ }
+
+ String category = getPackageCategory(packageName);
+ if (mFacetCategoryMap.containsKey(category)) {
+ int index = mFacetCategoryMap.get(packageName);
+ mFacetHasMultipleAppsCache.put(index, facetHasMultiplePackages(index));
+ }
+ }
+
private void bind() {
// Read up arrays_car.xml and populate the navigation bar here.
Resources r = mContext.getResources();
@@ -138,6 +155,7 @@
initFacetFilterMaps(i,
facetPackageNames.getString(i).split(FACET_FILTER_DEMILITER),
facetCategories.getString(i).split(FACET_FILTER_DEMILITER));
+ mFacetHasMultipleAppsCache.put(i, facetHasMultiplePackages(i));
} catch (URISyntaxException e) {
throw new RuntimeException("Malformed intent uri", e);
}
@@ -229,7 +247,7 @@
if (mNavButtons.get(index) != null) {
mNavButtons.get(index).setSelected(true /* selected */,
- facetHasMultiplePackages(index) /* showMoreIcon */);
+ mFacetHasMultipleAppsCache.get(index) /* showMoreIcon */);
}
mCurrentFacetIndex = index;
}
@@ -268,7 +286,7 @@
private void startActivity(Intent intent) {
if (mActivityStarter != null && intent != null) {
- mActivityStarter.startActivity(intent, true);
+ mActivityStarter.startActivity(intent, false);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 08cd053..c32ef0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -18,7 +18,10 @@
import android.app.ActivityManager;
import android.app.ITaskStackListener;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.graphics.PixelFormat;
import android.os.Handler;
import android.os.Looper;
@@ -52,6 +55,7 @@
mTaskStackListener = new TaskStackListenerImpl(mHandler);
mSystemServicesProxy = new SystemServicesProxy(mContext);
mSystemServicesProxy.registerTaskStackListener(mTaskStackListener);
+ registerPackageChangeReceivers();
}
@Override
@@ -81,6 +85,26 @@
mController = new CarNavigationBarController(context, mCarNavigationBar,
this /* ActivityStarter*/);
mNavigationBarView = mCarNavigationBar;
+
+ }
+
+ private BroadcastReceiver mPackageChangeReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getData() == null || mController == null) {
+ return;
+ }
+ String packageName = intent.getData().getSchemeSpecificPart();
+ mController.onPackageChange(packageName);
+ }
+ };
+
+ private void registerPackageChangeReceivers() {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addDataScheme("package");
+ mContext.registerReceiver(mPackageChangeReceiver, filter);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ExpandableIndicator.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ExpandableIndicator.java
new file mode 100644
index 0000000..8c7c71f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ExpandableIndicator.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.content.Context;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+import com.android.systemui.R;
+
+public class ExpandableIndicator extends ImageView {
+
+ private boolean mExpanded;
+
+ public ExpandableIndicator(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ final int res = mExpanded ? R.drawable.ic_volume_collapse_animation
+ : R.drawable.ic_volume_expand_animation;
+ setImageResource(res);
+ }
+
+ public void setExpanded(boolean expanded) {
+ if (expanded == mExpanded) return;
+ mExpanded = expanded;
+ final int res = mExpanded ? R.drawable.ic_volume_expand_animation
+ : R.drawable.ic_volume_collapse_animation;
+ // workaround to reset drawable
+ final AnimatedVectorDrawable avd = (AnimatedVectorDrawable) getContext()
+ .getDrawable(res).getConstantState().newDrawable();
+ setImageDrawable(avd);
+ avd.start();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
index d625fc2..260c969 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -163,68 +163,29 @@
String[] start = sets[0].split(BUTTON_SEPARATOR);
String[] center = sets[1].split(BUTTON_SEPARATOR);
String[] end = sets[2].split(BUTTON_SEPARATOR);
- inflateButtons(start, (ViewGroup) mRot0.findViewById(R.id.ends_group),
- (ViewGroup) mRot0.findViewById(R.id.ends_group_lightsout), false);
- inflateButtons(start, (ViewGroup) mRot90.findViewById(R.id.ends_group),
- (ViewGroup) mRot90.findViewById(R.id.ends_group_lightsout), true);
+ inflateButtons(start, (ViewGroup) mRot0.findViewById(R.id.ends_group), false);
+ inflateButtons(start, (ViewGroup) mRot90.findViewById(R.id.ends_group), true);
- inflateButtons(center, (ViewGroup) mRot0.findViewById(R.id.center_group),
- (ViewGroup) mRot0.findViewById(R.id.center_group_lightsout), false);
- inflateButtons(center, (ViewGroup) mRot90.findViewById(R.id.center_group),
- (ViewGroup) mRot90.findViewById(R.id.center_group_lightsout), true);
+ inflateButtons(center, (ViewGroup) mRot0.findViewById(R.id.center_group), false);
+ inflateButtons(center, (ViewGroup) mRot90.findViewById(R.id.center_group), true);
addGravitySpacer((LinearLayout) mRot0.findViewById(R.id.ends_group));
addGravitySpacer((LinearLayout) mRot90.findViewById(R.id.ends_group));
- inflateButtons(end, (ViewGroup) mRot0.findViewById(R.id.ends_group),
- (ViewGroup) mRot0.findViewById(R.id.ends_group_lightsout), false);
- inflateButtons(end, (ViewGroup) mRot90.findViewById(R.id.ends_group),
- (ViewGroup) mRot90.findViewById(R.id.ends_group_lightsout), true);
+ inflateButtons(end, (ViewGroup) mRot0.findViewById(R.id.ends_group), false);
+ inflateButtons(end, (ViewGroup) mRot90.findViewById(R.id.ends_group), true);
}
private void addGravitySpacer(LinearLayout layout) {
layout.addView(new Space(mContext), new LinearLayout.LayoutParams(0, 0, 1));
}
- private void inflateButtons(String[] buttons, ViewGroup parent, ViewGroup lightsOutParent,
- boolean landscape) {
+ private void inflateButtons(String[] buttons, ViewGroup parent, boolean landscape) {
for (int i = 0; i < buttons.length; i++) {
- copyToLightsout(inflateButton(buttons[i], parent, landscape), lightsOutParent);
+ inflateButton(buttons[i], parent, landscape);
}
}
- private void copyToLightsout(View view, ViewGroup lightsOutParent) {
- if (view == null) return;
-
- if (view instanceof FrameLayout) {
- // The only ViewGroup we support in here is a FrameLayout, so copy those manually.
- FrameLayout original = (FrameLayout) view;
- FrameLayout layout = new FrameLayout(view.getContext());
- for (int i = 0; i < original.getChildCount(); i++) {
- copyToLightsout(original.getChildAt(i), layout);
- }
- lightsOutParent.addView(layout, copy(view.getLayoutParams()));
- } else if (view instanceof Space) {
- lightsOutParent.addView(new Space(view.getContext()), copy(view.getLayoutParams()));
- } else {
- lightsOutParent.addView(generateLightsOutView(view), copy(view.getLayoutParams()));
- }
- }
-
- private View generateLightsOutView(View view) {
- ImageView imageView = new ImageView(view.getContext());
- // Copy everything we can about the original view.
- imageView.setPadding(view.getPaddingLeft(), view.getPaddingTop(), view.getPaddingRight(),
- view.getPaddingBottom());
- imageView.setContentDescription(view.getContentDescription());
- imageView.setId(view.getId());
- // Only home gets a big dot, everything else will be little.
- imageView.setImageResource(view.getId() == R.id.home
- ? R.drawable.ic_sysbar_lights_out_dot_large
- : R.drawable.ic_sysbar_lights_out_dot_small);
- return imageView;
- }
-
private ViewGroup.LayoutParams copy(ViewGroup.LayoutParams layoutParams) {
if (layoutParams instanceof LinearLayout.LayoutParams) {
return new LinearLayout.LayoutParams(layoutParams.width, layoutParams.height,
@@ -348,9 +309,7 @@
}
}
clearAllChildren((ViewGroup) mRot0.findViewById(R.id.nav_buttons));
- clearAllChildren((ViewGroup) mRot0.findViewById(R.id.lights_out));
clearAllChildren((ViewGroup) mRot90.findViewById(R.id.nav_buttons));
- clearAllChildren((ViewGroup) mRot90.findViewById(R.id.lights_out));
}
private void clearAllChildren(ViewGroup group) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
index 134c579..1fe0115 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
@@ -64,42 +64,20 @@
mLightsOut = lightsOut;
final View navButtons = mView.getCurrentView().findViewById(R.id.nav_buttons);
- final View lowLights = mView.getCurrentView().findViewById(R.id.lights_out);
// ok, everyone, stop it right there
navButtons.animate().cancel();
- lowLights.animate().cancel();
- final float navButtonsAlpha = lightsOut ? 0f : 1f;
- final float lowLightsAlpha = lightsOut ? 1f : 0f;
+ final float navButtonsAlpha = lightsOut ? 0.5f : 1f;
if (!animate) {
navButtons.setAlpha(navButtonsAlpha);
- lowLights.setAlpha(lowLightsAlpha);
- lowLights.setVisibility(lightsOut ? View.VISIBLE : View.GONE);
} else {
final int duration = lightsOut ? LIGHTS_OUT_DURATION : LIGHTS_IN_DURATION;
navButtons.animate()
.alpha(navButtonsAlpha)
.setDuration(duration)
.start();
-
- lowLights.setOnTouchListener(mLightsOutListener);
- if (lowLights.getVisibility() == View.GONE) {
- lowLights.setAlpha(0f);
- lowLights.setVisibility(View.VISIBLE);
- }
- lowLights.animate()
- .alpha(lowLightsAlpha)
- .setDuration(duration)
- .setInterpolator(new AccelerateInterpolator(2.0f))
- .setListener(lightsOut ? null : new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator _a) {
- lowLights.setVisibility(View.GONE);
- }
- })
- .start();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 97d7dd5..6698076 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -79,7 +79,7 @@
private Drawable mBackAltCarModeIcon, mBackAltLandCarModeIcon;
private Drawable mHomeDefaultIcon, mHomeCarModeIcon;
private Drawable mRecentIcon;
- private Drawable mRecentLandIcon;
+ private Drawable mDockedIcon;
private NavigationBarGestureHelper mGestureHelper;
private DeadZone mDeadZone;
@@ -97,6 +97,7 @@
private boolean mLayoutTransitionsEnabled = true;
private boolean mWakeAndUnlocking;
private boolean mCarMode = false;
+ private boolean mDockedStackExists;
private final SparseArray<ButtonDispatcher> mButtonDisatchers = new SparseArray<>();
@@ -280,7 +281,7 @@
mHomeDefaultIcon = ctx.getDrawable(R.drawable.ic_sysbar_home);
mRecentIcon = ctx.getDrawable(R.drawable.ic_sysbar_recent);
- mRecentLandIcon = mRecentIcon;
+ mDockedIcon = ctx.getDrawable(R.drawable.ic_sysbar_docked);
getCarModeIcons(ctx);
}
@@ -335,8 +336,7 @@
getBackButton().setImageDrawable(backIcon);
- getRecentsButton().setImageDrawable(
- mVertical ? mRecentLandIcon : mRecentIcon);
+ updateRecentsIcon();
if (mCarMode) {
getHomeButton().setImageDrawable(mHomeCarModeIcon);
@@ -507,7 +507,8 @@
mHandler.post(new Runnable() {
@Override
public void run() {
- updateRecentsIcon(exists);
+ mDockedStackExists = exists;
+ updateRecentsIcon();
}
});
}
@@ -517,10 +518,8 @@
}
}
- private void updateRecentsIcon(boolean dockedStackExists) {
- getRecentsButton().setImageResource(dockedStackExists
- ? R.drawable.ic_sysbar_docked
- : R.drawable.ic_sysbar_recent);
+ private void updateRecentsIcon() {
+ getRecentsButton().setImageDrawable(mDockedStackExists ? mDockedIcon : mRecentIcon);
}
public boolean isVertical() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 3aa576f..401d405 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -318,7 +318,7 @@
StatusBarIconController mIconController;
// expanded notifications
- NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
+ protected NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
View mExpandedContents;
TextView mNotificationPanelDebugText;
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 11d99ff..bd5bac2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -20,55 +20,76 @@
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
import android.util.AttributeSet;
import android.view.View;
+import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.android.keyguard.KeyguardStatusView;
+import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.QuickQSPanel;
+import com.android.systemui.qs.TouchAnimator;
+import com.android.systemui.qs.TouchAnimator.Listener;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.tuner.TunerService;
public class QuickStatusBarHeader extends BaseStatusBarHeader implements
- NextAlarmController.NextAlarmChangeCallback, View.OnClickListener {
+ NextAlarmChangeCallback, OnClickListener, Listener {
private static final String TAG = "QuickStatusBarHeader";
+
+ private static final float EXPAND_INDICATOR_THRESHOLD = .8f;
+
private ActivityStarter mActivityStarter;
private NextAlarmController mNextAlarmController;
private SettingsButton mSettingsButton;
private View mSettingsContainer;
+
private TextView mAlarmStatus;
+ private TextView mAlarmStatusCollapsed;
private QSPanel mQsPanel;
private boolean mExpanded;
private boolean mAlarmShowing;
- private ViewGroup mExpandedGroup;
private ViewGroup mDateTimeGroup;
- private View mEmergencyOnly;
- private TextView mQsDetailHeaderTitle;
+ private ViewGroup mDateTimeAlarmGroup;
+ private TextView mEmergencyOnly;
+
+ private ExpandableIndicator mExpandIndicator;
+
private boolean mListening;
private AlarmManager.AlarmClockInfo mNextAlarm;
private QuickQSPanel mHeaderQsPanel;
private boolean mShowEmergencyCallsOnly;
- private float mDateTimeTranslation;
private MultiUserSwitch mMultiUserSwitch;
private ImageView mMultiUserAvatar;
- private View mQsDetailHeaderBack;
- private final int[] mTmpInt2 = new int[2];
+ private float mDateTimeTranslation;
+ private float mDateTimeAlarmTranslation;
+ private float mDateScaleFactor;
+ private float mGearTranslation;
+
+ private TouchAnimator mAnimator;
+ private TouchAnimator mSecondHalfAnimator;
+ private TouchAnimator mFirstHalfAnimator;
+ private TouchAnimator mDateSizeAnimator;
+ private TouchAnimator mAlarmTranslation;
+ private float mExpansionAmount;
public QuickStatusBarHeader(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -78,12 +99,15 @@
protected void onFinishInflate() {
super.onFinishInflate();
- mEmergencyOnly = findViewById(R.id.header_emergency_calls_only);
- mDateTimeTranslation = mContext.getResources().getDimension(
- R.dimen.qs_date_anim_translation);
+ mEmergencyOnly = (TextView) findViewById(R.id.header_emergency_calls_only);
+
+ mDateTimeAlarmGroup = (ViewGroup) findViewById(R.id.date_time_alarm_group);
+ mDateTimeAlarmGroup.findViewById(R.id.empty_time_view).setVisibility(View.GONE);
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);
+ mDateTimeGroup.setPivotX(0);
+ mDateTimeGroup.setPivotY(0);
+
+ mExpandIndicator = (ExpandableIndicator) findViewById(R.id.expand_indicator);
mHeaderQsPanel = (QuickQSPanel) findViewById(R.id.quick_qs_panel);
@@ -91,6 +115,7 @@
mSettingsContainer = findViewById(R.id.settings_button_container);
mSettingsButton.setOnClickListener(this);
+ mAlarmStatusCollapsed = (TextView) findViewById(R.id.alarm_status_collapsed);
mAlarmStatus = (TextView) findViewById(R.id.alarm_status);
mAlarmStatus.setOnClickListener(this);
@@ -110,6 +135,54 @@
getHeight()));
}
});
+ updateResources();
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ updateResources();
+ }
+
+ private void updateResources() {
+ FontSizeUtils.updateFontSize(mAlarmStatus, R.dimen.qs_date_collapsed_size);
+ FontSizeUtils.updateFontSize(mEmergencyOnly, R.dimen.qs_emergency_calls_only_text_size);
+
+ mGearTranslation = mContext.getResources().getDimension(R.dimen.qs_header_gear_translation);
+
+ mDateTimeTranslation = mContext.getResources().getDimension(
+ R.dimen.qs_date_anim_translation);
+ mDateTimeAlarmTranslation = mContext.getResources().getDimension(
+ R.dimen.qs_date_alarm_anim_translation);
+ float dateCollapsedSize = mContext.getResources().getDimension(
+ R.dimen.qs_date_collapsed_text_size);
+ float dateExpandedSize = mContext.getResources().getDimension(
+ R.dimen.qs_date_text_size);
+ mDateScaleFactor = dateExpandedSize / dateCollapsedSize;
+ updateDateTimePosition();
+
+ mAnimator = new TouchAnimator.Builder()
+ .addFloat(mSettingsContainer, "translationY", -mGearTranslation, 0)
+ .addFloat(mMultiUserSwitch, "translationY", -mGearTranslation, 0)
+ .addFloat(mSettingsButton, "rotation", -90, 0)
+ .setListener(this)
+ .build();
+ mSecondHalfAnimator = new TouchAnimator.Builder()
+ .addFloat(mSettingsButton, "rotation", -90, 0)
+ .addFloat(mAlarmStatus, "alpha", 0, 1)
+ .addFloat(mEmergencyOnly, "alpha", 0, 1)
+ .setStartDelay(.5f)
+ .build();
+ mFirstHalfAnimator = new TouchAnimator.Builder()
+ .addFloat(mAlarmStatusCollapsed, "alpha", 1, 0)
+ .addFloat(mHeaderQsPanel, "alpha", 1, 0)
+ .setEndDelay(.5f)
+ .build();
+ mDateSizeAnimator = new TouchAnimator.Builder()
+ .addFloat(mDateTimeGroup, "scaleX", 1, mDateScaleFactor)
+ .addFloat(mDateTimeGroup, "scaleY", 1, mDateScaleFactor)
+ .setStartDelay(.36f)
+ .build();
}
@Override
@@ -134,19 +207,52 @@
if (nextAlarm != null) {
mAlarmStatus.setText(KeyguardStatusView.formatNextAlarm(getContext(), nextAlarm));
}
- mAlarmShowing = nextAlarm != null;
- updateEverything();
+ if (mAlarmShowing != (nextAlarm != null)) {
+ mAlarmShowing = nextAlarm != null;
+ updateEverything();
+ }
}
@Override
public void setExpansion(float headerExpansionFraction) {
- mExpandedGroup.setAlpha(headerExpansionFraction);
- mExpandedGroup.setVisibility(headerExpansionFraction > 0 ? View.VISIBLE : View.INVISIBLE);
- mHeaderQsPanel.setAlpha(1 - headerExpansionFraction);
- mHeaderQsPanel.setVisibility(headerExpansionFraction < 1 ? View.VISIBLE : View.INVISIBLE);
+ mExpansionAmount = headerExpansionFraction;
+ mAnimator.setPosition(headerExpansionFraction);
+ mSecondHalfAnimator.setPosition(headerExpansionFraction);
+ mFirstHalfAnimator.setPosition(headerExpansionFraction);
+ mDateSizeAnimator.setPosition(headerExpansionFraction);
+ mAlarmTranslation.setPosition(headerExpansionFraction);
- mDateTimeGroup.setTranslationY(headerExpansionFraction * mDateTimeTranslation);
- mEmergencyOnly.setAlpha(headerExpansionFraction);
+ updateAlarmVisibilities();
+
+ mExpandIndicator.setExpanded(headerExpansionFraction > EXPAND_INDICATOR_THRESHOLD);
+ }
+
+ @Override
+ public void onAnimationAtStart() {
+ }
+
+ @Override
+ public void onAnimationAtEnd() {
+ mHeaderQsPanel.setVisibility(View.INVISIBLE);
+ }
+
+ @Override
+ public void onAnimationStarted() {
+ mHeaderQsPanel.setVisibility(View.VISIBLE);
+ }
+
+ private void updateAlarmVisibilities() {
+ mAlarmStatus.setVisibility(mAlarmShowing ? View.VISIBLE : View.INVISIBLE);
+ mAlarmStatusCollapsed.setVisibility(mAlarmShowing ? View.VISIBLE : View.INVISIBLE);
+ }
+
+ private void updateDateTimePosition() {
+ // This one has its own because we have to rebuild it every time the alarm state changes.
+ mAlarmTranslation = new TouchAnimator.Builder()
+ .addFloat(mDateTimeAlarmGroup, "translationY", 0, mAlarmShowing
+ ? mDateTimeAlarmTranslation : mDateTimeTranslation)
+ .build();
+ mAlarmTranslation.setPosition(mExpansionAmount);
}
public void setListening(boolean listening) {
@@ -160,11 +266,12 @@
@Override
public void updateEverything() {
+ updateDateTimePosition();
updateVisibilities();
}
private void updateVisibilities() {
- mAlarmStatus.setVisibility(mAlarmShowing ? View.VISIBLE : View.GONE);
+ updateAlarmVisibilities();
mEmergencyOnly.setVisibility(mExpanded && mShowEmergencyCallsOnly
? View.VISIBLE : View.INVISIBLE);
mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
index 5ded885..ad42459 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
@@ -117,13 +117,9 @@
if (isRotated) {
mPreview.findViewById(R.id.rot0).setVisibility(View.GONE);
final View rot90 = mPreview.findViewById(R.id.rot90);
- rot90.findViewById(R.id.ends_group_lightsout).setVisibility(View.GONE);
- rot90.findViewById(R.id.center_group_lightsout).setVisibility(View.GONE);
} else {
mPreview.findViewById(R.id.rot90).setVisibility(View.GONE);
final View rot0 = mPreview.findViewById(R.id.rot0);
- rot0.findViewById(R.id.ends_group_lightsout).setVisibility(View.GONE);
- rot0.findViewById(R.id.center_group_lightsout).setVisibility(View.GONE);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerZenModePanel.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerZenModePanel.java
new file mode 100644
index 0000000..cc0ffb0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerZenModePanel.java
@@ -0,0 +1,141 @@
+/*
+ * 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.tuner;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Checkable;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import com.android.systemui.Prefs;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.volume.ZenModePanel;
+import com.android.systemui.volume.ZenModePanel.Callback;
+
+public class TunerZenModePanel extends LinearLayout implements OnClickListener {
+ private static final String TAG = "TunerZenModePanel";
+
+ private Callback mCallback;
+ private ZenModePanel mZenModePanel;
+ private View mHeaderSwitch;
+ private int mZenMode;
+ private ZenModeController mController;
+ private View mButtons;
+ private View mMoreSettings;
+ private View mDone;
+ private OnClickListener mDoneListener;
+ private boolean mEditing;
+
+ public TunerZenModePanel(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public void init(ZenModeController zenModeController) {
+ mController = zenModeController;
+ mHeaderSwitch = findViewById(R.id.tuner_zen_switch);
+ mHeaderSwitch.setVisibility(View.VISIBLE);
+ mHeaderSwitch.setOnClickListener(this);
+ mHeaderSwitch.findViewById(com.android.internal.R.id.up).setVisibility(View.GONE);
+ ((TextView) mHeaderSwitch.findViewById(android.R.id.title)).setText(
+ R.string.quick_settings_dnd_label);
+ mZenModePanel = (ZenModePanel) findViewById(R.id.zen_mode_panel);
+ mZenModePanel.init(zenModeController);
+ mButtons = findViewById(R.id.tuner_zen_buttons);
+ mMoreSettings = mButtons.findViewById(android.R.id.button2);
+ mMoreSettings.setOnClickListener(this);
+ ((TextView) mMoreSettings).setText(R.string.quick_settings_more_settings);
+ mDone = mButtons.findViewById(android.R.id.button1);
+ mDone.setOnClickListener(this);
+ ((TextView) mDone).setText(R.string.quick_settings_done);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mEditing = false;
+ }
+
+ public void setCallback(Callback zenPanelCallback) {
+ mCallback = zenPanelCallback;
+ mZenModePanel.setCallback(zenPanelCallback);
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v == mHeaderSwitch) {
+ mEditing = true;
+ if (mZenMode == Global.ZEN_MODE_OFF) {
+ mZenMode = Prefs.getInt(mContext, Prefs.Key.DND_FAVORITE_ZEN,
+ Global.ZEN_MODE_ALARMS);
+ mController.setZen(mZenMode, null, TAG);
+ postUpdatePanel();
+ } else {
+ mZenMode = Global.ZEN_MODE_OFF;
+ mController.setZen(Global.ZEN_MODE_OFF, null, TAG);
+ postUpdatePanel();
+ }
+ } else if (v == mMoreSettings) {
+ Intent intent = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ getContext().startActivity(intent);
+ } else if (v == mDone) {
+ mEditing = false;
+ setVisibility(View.GONE);
+ mDoneListener.onClick(v);
+ }
+ }
+
+ public boolean isEditing() {
+ return mEditing;
+ }
+
+ public void setZenState(int zenMode) {
+ mZenMode = zenMode;
+ postUpdatePanel();
+ }
+
+ private void postUpdatePanel() {
+ // The complicated structure from reusing the same ZenPanel has resulted in some
+ // unstableness/flickering from callbacks coming in quickly. To solve this just
+ // post the UI updates a little bit.
+ removeCallbacks(mUpdate);
+ postDelayed(mUpdate, 40);
+ }
+
+ public void setDoneListener(OnClickListener onClickListener) {
+ mDoneListener = onClickListener;
+ }
+
+ private void updatePanel() {
+ boolean zenOn = mZenMode != Global.ZEN_MODE_OFF;
+ ((Checkable) mHeaderSwitch.findViewById(android.R.id.toggle)).setChecked(zenOn);
+ mZenModePanel.setVisibility(zenOn ? View.VISIBLE : View.GONE);
+ mButtons.setVisibility(zenOn ? View.VISIBLE : View.GONE);
+ }
+
+ private final Runnable mUpdate = new Runnable() {
+ @Override
+ public void run() {
+ updatePanel();
+ }
+ };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
index b407935..56a604d 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
@@ -37,7 +37,8 @@
private View mGuideOverlayView;
private final Runnable mHideGuideOverlayRunnable = new Runnable() {
public void run() {
- mGuideOverlayView.setVisibility(View.INVISIBLE);
+ // TODO: Uncomment this after the b/27224884 is fixed.
+ //mGuideOverlayView.setVisibility(View.INVISIBLE);
}
};
@@ -50,8 +51,9 @@
}
@Override
- protected void onStart() {
- super.onStart();
+ protected void onResume() {
+ super.onResume();
+ mGuideOverlayView.setVisibility(View.VISIBLE);
mHandler.removeCallbacks(mHideGuideOverlayRunnable);
mHandler.postDelayed(mHideGuideOverlayRunnable, SHOW_GUIDE_OVERLAY_VIEW_DURATION_MS);
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
index 6aae9bd..1810c1c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
@@ -67,6 +67,7 @@
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
+import com.android.systemui.tuner.TunerZenModePanel;
import com.android.systemui.volume.VolumeDialogController.State;
import com.android.systemui.volume.VolumeDialogController.StreamState;
@@ -133,7 +134,7 @@
private int mLastActiveStream;
private boolean mShowFullZen;
- private final ZenModePanel mZenPanel;
+ private final TunerZenModePanel mZenPanel;
public VolumeDialog(Context context, int windowType, VolumeDialogController controller,
ZenModeController zenModeController, Callback callback) {
@@ -225,8 +226,7 @@
mExpandButtonAnimationDuration = res.getInteger(R.integer.volume_expand_animation_duration);
mZenFooter = (ZenFooter) mDialog.findViewById(R.id.volume_zen_footer);
mZenFooter.init(zenModeController);
- mZenPanel = (ZenModePanel) mDialog.findViewById(R.id.zen_mode_panel);
- mZenPanel.addNoneButton();
+ mZenPanel = (TunerZenModePanel) mDialog.findViewById(R.id.tuner_zen_mode_panel);
mZenPanel.init(zenModeController);
mZenPanel.setCallback(mZenPanelCallback);
@@ -671,7 +671,7 @@
final boolean wasVisible = mZenFooter.getVisibility() == View.VISIBLE;
final boolean visible = mState.zenMode != Global.ZEN_MODE_OFF
&& (mAudioManager.isStreamAffectedByRingerMode(mActiveStream) || mExpanded)
- && !mShowFullZen;
+ && !mZenPanel.isEditing();
if (wasVisible != visible && !visible) {
prepareForCollapse();
}
@@ -679,12 +679,21 @@
mZenFooter.update();
final boolean fullWasVisible = mZenPanel.getVisibility() == View.VISIBLE;
- final boolean fullVisible = mShowFullZen && (mState.zenMode != Global.ZEN_MODE_OFF
- || mExpanded);
+ final boolean fullVisible = mShowFullZen && !visible;
if (fullWasVisible != fullVisible && !fullVisible) {
prepareForCollapse();
}
Util.setVisOrGone(mZenPanel, fullVisible);
+ if (fullVisible) {
+ mZenPanel.setZenState(mState.zenMode);
+ mZenPanel.setDoneListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ prepareForCollapse();
+ mHandler.sendEmptyMessage(H.UPDATE_FOOTER);
+ }
+ });
+ }
}
private void updateVolumeRowH(VolumeRow row) {
@@ -978,6 +987,7 @@
private static final int RESCHEDULE_TIMEOUT = 6;
private static final int STATE_CHANGED = 7;
private static final int UPDATE_BOTTOM_MARGIN = 8;
+ private static final int UPDATE_FOOTER = 9;
public H() {
super(Looper.getMainLooper());
@@ -994,6 +1004,7 @@
case RESCHEDULE_TIMEOUT: rescheduleTimeoutH(); break;
case STATE_CHANGED: onStateChangedH(mState); break;
case UPDATE_BOTTOM_MARGIN: updateDialogBottomMarginH(); break;
+ case UPDATE_FOOTER: updateFooterH(); break;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index 5ca24f7..6976c0b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -190,12 +190,6 @@
mZenAlarmWarning = (TextView) findViewById(R.id.zen_alarm_warning);
}
- public void addNoneButton() {
- mZenButtons.addButton(R.string.interruption_level_all_twoline,
- R.string.interruption_level_all,
- Global.ZEN_MODE_OFF);
- }
-
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 3335315..3e7466f 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -275,6 +275,9 @@
private void processBatchedEvents(long frameNanos) {
MotionEventHolder current = mEventQueue;
+ if (current == null) {
+ return;
+ }
while (current.next != null) {
current = current.next;
}
@@ -403,6 +406,9 @@
}
private void disableFeatures() {
+ // Give the features a chance to process any batched events so we'll keep a consistent
+ // event stream
+ processBatchedEvents(Long.MAX_VALUE);
if (mMotionEventInjector != null) {
mAms.setMotionEventInjector(null);
mMotionEventInjector.onDestroy();
diff --git a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
index 9e6cd00..3ecff40 100644
--- a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
@@ -467,17 +467,18 @@
if (mCurrentState == STATE_GESTURE_DETECTING) {
endGestureDetection();
} else if (mCurrentState == STATE_TOUCH_EXPLORING) {
- final int pointerId = mReceivedPointerTracker.getPrimaryPointerId();
- final int pointerIdBits = (1 << pointerId);
+ // If the finger is still moving, pass the event on.
+ if (event.getActionMasked() == MotionEvent.ACTION_MOVE) {
+ final int pointerId = mReceivedPointerTracker.getPrimaryPointerId();
+ final int pointerIdBits = (1 << pointerId);
- // Cache the event until we discern exploration from gesturing.
- mSendHoverEnterAndMoveDelayed.addEvent(event);
-
- // We have just decided that the user is touch,
- // exploring so start sending events.
- mSendHoverEnterAndMoveDelayed.forceSendAndRemove();
- mSendHoverExitDelayed.cancel();
- sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags);
+ // We have just decided that the user is touch,
+ // exploring so start sending events.
+ mSendHoverEnterAndMoveDelayed.addEvent(event);
+ mSendHoverEnterAndMoveDelayed.forceSendAndRemove();
+ mSendHoverExitDelayed.cancel();
+ sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags);
+ }
}
}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index eaee1d3..a4fc2ec 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -191,7 +191,8 @@
// 1 : initial release
// 2 : no format change per se; version bump to facilitate PBKDF2 version skew detection
// 3 : introduced "_meta" metadata file; no other format change per se
- static final int BACKUP_FILE_VERSION = 3;
+ // 4 : added support for new device-encrypted storage locations
+ static final int BACKUP_FILE_VERSION = 4;
static final String BACKUP_FILE_HEADER_MAGIC = "ANDROID BACKUP\n";
static final int BACKUP_PW_FILE_VERSION = 2;
static final String BACKUP_METADATA_FILENAME = "_meta";
@@ -347,13 +348,13 @@
}
@Override
- public void onBootPhase(int phase) {
- if (phase == PHASE_SYSTEM_SERVICES_READY) {
- sInstance.initialize(UserHandle.USER_SYSTEM);
- } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+ public void onUnlockUser(int userId) {
+ if (userId == UserHandle.USER_SYSTEM) {
+ sInstance.initialize(userId);
+
ContentResolver r = sInstance.mContext.getContentResolver();
- boolean areEnabled = Settings.Secure.getInt(r,
- Settings.Secure.BACKUP_ENABLED, 0) != 0;
+ boolean areEnabled = Settings.Secure.getIntForUser(r,
+ Settings.Secure.BACKUP_ENABLED, 0, userId) != 0;
try {
sInstance.setBackupEnabled(areEnabled);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 499b706..6d0d9e9 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -1672,6 +1672,7 @@
@Override
public void dump(FileDescriptor fd, PrintWriter writer, String args[]) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
if (mBluetoothBinder == null) {
writer.println("Bluetooth Service not connected");
} else {
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index f522288..507ac22 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -940,35 +940,18 @@
private void resetDefaultImeLocked(Context context) {
// Do not reset the default (current) IME when it is a 3rd-party IME
- if (mCurMethodId != null
- && !InputMethodUtils.isSystemIme(mMethodMap.get(mCurMethodId))) {
+ if (mCurMethodId != null && !InputMethodUtils.isSystemIme(mMethodMap.get(mCurMethodId))) {
return;
}
-
- InputMethodInfo defIm = null;
- for (InputMethodInfo imi : mMethodList) {
- if (defIm == null && mSystemReady) {
- final Locale systemLocale = context.getResources().getConfiguration().locale;
- if (InputMethodUtils.isSystemImeThatHasSubtypeOf(imi, context,
- true /* checkDefaultAttribute */, systemLocale, false /* checkCountry */,
- InputMethodUtils.SUBTYPE_MODE_ANY)) {
- defIm = imi;
- Slog.i(TAG, "Selected default: " + imi.getId());
- }
- }
+ final List<InputMethodInfo> suitableImes = InputMethodUtils.getDefaultEnabledImes(
+ context, mSystemReady, mSettings.getEnabledInputMethodListLocked());
+ if (suitableImes.isEmpty()) {
+ Slog.i(TAG, "No default found");
+ return;
}
- if (defIm == null && mMethodList.size() > 0) {
- defIm = InputMethodUtils.getMostApplicableDefaultIME(
- mSettings.getEnabledInputMethodListLocked());
- if (defIm != null) {
- Slog.i(TAG, "Default found, using " + defIm.getId());
- } else {
- Slog.i(TAG, "No default found");
- }
- }
- if (defIm != null) {
- setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false);
- }
+ final InputMethodInfo defIm = suitableImes.get(0);
+ Slog.i(TAG, "Default found, using " + defIm.getId());
+ setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false);
}
private void resetAllInternalStateLocked(final boolean updateOnlyWhenLocaleChanged,
@@ -3004,6 +2987,25 @@
}
}
+ // TODO: The following code should find better place to live.
+ if (!resetDefaultEnabledIme) {
+ boolean enabledImeFound = false;
+ final List<InputMethodInfo> enabledImes = mSettings.getEnabledInputMethodListLocked();
+ final int N = enabledImes.size();
+ for (int i = 0; i < N; ++i) {
+ final InputMethodInfo imi = enabledImes.get(i);
+ if (mMethodList.contains(imi)) {
+ enabledImeFound = true;
+ break;
+ }
+ }
+ if (!enabledImeFound) {
+ Slog.i(TAG, "All the enabled IMEs are gone. Reset default enabled IMEs.");
+ resetDefaultEnabledIme = true;
+ resetSelectedInputMethodAndSubtypeLocked("");
+ }
+ }
+
if (resetDefaultEnabledIme) {
final ArrayList<InputMethodInfo> defaultEnabledIme =
InputMethodUtils.getDefaultEnabledImes(mContext, mSystemReady, mMethodList);
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index ca1e371..163b9be 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -660,7 +660,12 @@
long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
- return readUserDataInternal(accounts, account, key);
+ synchronized (accounts.cacheLock) {
+ if (!accountExistsCacheLocked(accounts, account)) {
+ return null;
+ }
+ return readUserDataInternalLocked(accounts, account, key);
+ }
} finally {
restoreCallingIdentity(identityToken);
}
@@ -1716,44 +1721,58 @@
long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
- setUserdataInternal(accounts, account, key, value);
+ synchronized (accounts.cacheLock) {
+ if (!accountExistsCacheLocked(accounts, account)) {
+ return;
+ }
+ setUserdataInternalLocked(accounts, account, key, value);
+ }
} finally {
restoreCallingIdentity(identityToken);
}
}
- private void setUserdataInternal(UserAccounts accounts, Account account, String key,
+ private boolean accountExistsCacheLocked(UserAccounts accounts, Account account) {
+ if (accounts.accountCache.containsKey(account.type)) {
+ for (Account acc : getUserAccountsForCaller().accountCache.get(account.type)) {
+ if (acc.name.equals(account.name)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private void setUserdataInternalLocked(UserAccounts accounts, Account account, String key,
String value) {
if (account == null || key == null) {
return;
}
- synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
- db.beginTransaction();
- try {
- long accountId = getAccountIdLocked(db, account);
- if (accountId < 0) {
+ final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ long accountId = getAccountIdLocked(db, account);
+ if (accountId < 0) {
+ return;
+ }
+ long extrasId = getExtrasIdLocked(db, accountId, key);
+ if (extrasId < 0) {
+ extrasId = insertExtraLocked(db, accountId, key, value);
+ if (extrasId < 0) {
return;
}
- long extrasId = getExtrasIdLocked(db, accountId, key);
- if (extrasId < 0 ) {
- extrasId = insertExtraLocked(db, accountId, key, value);
- if (extrasId < 0) {
- return;
- }
- } else {
- ContentValues values = new ContentValues();
- values.put(EXTRAS_VALUE, value);
- if (1 != db.update(TABLE_EXTRAS, values, EXTRAS_ID + "=" + extrasId, null)) {
- return;
- }
-
+ } else {
+ ContentValues values = new ContentValues();
+ values.put(EXTRAS_VALUE, value);
+ if (1 != db.update(TABLE_EXTRAS, values, EXTRAS_ID + "=" + extrasId, null)) {
+ return;
}
- writeUserDataIntoCacheLocked(accounts, db, account, key, value);
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
+
}
+ writeUserDataIntoCacheLocked(accounts, db, account, key, value);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
}
}
@@ -4788,17 +4807,16 @@
}
}
- protected String readUserDataInternal(UserAccounts accounts, Account account, String key) {
- synchronized (accounts.cacheLock) {
- HashMap<String, String> userDataForAccount = accounts.userDataCache.get(account);
- if (userDataForAccount == null) {
- // need to populate the cache for this account
- final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
- userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account);
- accounts.userDataCache.put(account, userDataForAccount);
- }
- return userDataForAccount.get(key);
+ protected String readUserDataInternalLocked(
+ UserAccounts accounts, Account account, String key) {
+ HashMap<String, String> userDataForAccount = accounts.userDataCache.get(account);
+ if (userDataForAccount == null) {
+ // need to populate the cache for this account
+ final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
+ userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account);
+ accounts.userDataCache.put(account, userDataForAccount);
}
+ return userDataForAccount.get(key);
}
protected HashMap<String, String> readUserDataForAccountFromDatabaseLocked(
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ccaa1d2..c7f7378 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -6430,6 +6430,9 @@
if (mLockScreenShown == LOCK_SCREEN_SHOWN) {
mLockScreenShown = LOCK_SCREEN_HIDDEN;
updateSleepIfNeededLocked();
+
+ // Some stack visibility might change (e.g. docked stack)
+ mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
}
}
} finally {
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 6716a47..574b9db 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1902,7 +1902,7 @@
return false;
}
- cancelInitializingActivities();
+ mStackSupervisor.cancelInitializingActivities();
// Find the first activity that is not finishing.
final ActivityRecord next = topRunningActivityLocked();
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 662c51e..c143474 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -908,6 +908,15 @@
}
}
+ void cancelInitializingActivities() {
+ for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
+ ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
+ for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+ stacks.get(stackNdx).cancelInitializingActivities();
+ }
+ }
+ }
+
void reportActivityVisibleLocked(ActivityRecord r) {
sendWaitingVisibleReportLocked(r);
}
diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
index 5806f3f..a6325a4 100644
--- a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
@@ -20,6 +20,7 @@
import android.media.AudioRecordConfiguration;
import android.media.AudioSystem;
import android.media.IRecordingConfigDispatcher;
+import android.media.MediaRecorder;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
@@ -48,6 +49,9 @@
* Implementation of android.media.AudioSystem.AudioRecordingCallback
*/
public void onRecordingConfigurationChanged(int event, int session, int source) {
+ if (MediaRecorder.isSystemOnlyAudioSource(source)) {
+ return;
+ }
if (updateSnapshot(event, session, source)) {
final Iterator<RecMonitorClient> clientIterator = mClients.iterator();
synchronized(mClients) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 484b0e9..2ca5534 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -23,11 +23,13 @@
import android.app.Notification;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.Icon;
import android.media.AudioAttributes;
+import android.os.Build;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
@@ -146,6 +148,23 @@
importance = IMPORTANCE_DEFAULT;
}
+ try {
+ final ApplicationInfo applicationInfo =
+ mContext.getPackageManager().getApplicationInfoAsUser(sbn.getPackageName(),
+ 0, sbn.getUser().getIdentifier());
+ if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.N) {
+ if (isNoisy) {
+ if (importance >= IMPORTANCE_HIGH) {
+ importance = IMPORTANCE_MAX;
+ } else {
+ importance = IMPORTANCE_HIGH;
+ }
+ }
+ }
+ } catch (NameNotFoundException e) {
+ // oh well.
+ }
+
if (n.fullScreenIntent != null) {
importance = IMPORTANCE_MAX;
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 5db7e63..bf0073a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -65,6 +65,7 @@
import static android.content.pm.PackageManager.MATCH_ENCRYPTION_AWARE_AND_UNAWARE;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
+import static android.content.pm.PackageManager.MOVE_FAILED_DEVICE_ADMIN;
import static android.content.pm.PackageManager.MOVE_FAILED_DOESNT_EXIST;
import static android.content.pm.PackageManager.MOVE_FAILED_INTERNAL_ERROR;
import static android.content.pm.PackageManager.MOVE_FAILED_OPERATION_PENDING;
@@ -3339,11 +3340,6 @@
+ " with flags 0x" + Integer.toHexString(flags), new Throwable());
}
- // Safe mode means we shouldn't match any third-party components
- if (mSafeMode) {
- flags |= PackageManager.MATCH_SYSTEM_ONLY;
- }
-
return updateFlags(flags, userId);
}
@@ -3351,6 +3347,11 @@
* Update given flags when being used to request {@link ResolveInfo}.
*/
int updateFlagsForResolve(int flags, int userId, Object cookie) {
+ // Safe mode means we shouldn't match any third-party components
+ if (mSafeMode) {
+ flags |= PackageManager.MATCH_SYSTEM_ONLY;
+ }
+
return updateFlagsForComponent(flags, userId, cookie);
}
@@ -14104,6 +14105,11 @@
});
}
+ @Override
+ public boolean isPackageDeviceAdminOnAnyUser(String packageName) {
+ return isPackageDeviceAdmin(packageName, UserHandle.USER_ALL);
+ }
+
private boolean isPackageDeviceAdmin(String packageName, int userId) {
IDevicePolicyManager dpm = IDevicePolicyManager.Stub.asInterface(
ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
@@ -18170,6 +18176,10 @@
throw new PackageManagerException(MOVE_FAILED_INTERNAL_ERROR,
"Package already moved to " + volumeUuid);
}
+ if (pkg.applicationInfo.isInternal() && isPackageDeviceAdminOnAnyUser(packageName)) {
+ throw new PackageManagerException(MOVE_FAILED_DEVICE_ADMIN,
+ "Device admin cannot be moved");
+ }
if (ps.frozen) {
throw new PackageManagerException(MOVE_FAILED_OPERATION_PENDING,
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 76d6b28..117c663 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1749,16 +1749,6 @@
}
}
- private boolean isPackageInstalled(String pkg, int userId) {
- final ApplicationInfo info = mPm.getApplicationInfo(pkg,
- PackageManager.GET_UNINSTALLED_PACKAGES,
- userId);
- if (info == null || (info.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
- return false;
- }
- return true;
- }
-
/**
* Removes the app restrictions file for a specific package and user id, if it exists.
*/
@@ -2210,20 +2200,18 @@
}
}
- if (isPackageInstalled(packageName, userId)) {
- // Notify package of changes via an intent - only sent to explicitly registered receivers.
- Intent changeIntent = new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);
- changeIntent.setPackage(packageName);
- changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- mContext.sendBroadcastAsUser(changeIntent, new UserHandle(userId));
- }
+ // Notify package of changes via an intent - only sent to explicitly registered receivers.
+ Intent changeIntent = new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);
+ changeIntent.setPackage(packageName);
+ changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mContext.sendBroadcastAsUser(changeIntent, UserHandle.of(userId));
}
private int getUidForPackage(String packageName) {
long ident = Binder.clearCallingIdentity();
try {
return mContext.getPackageManager().getApplicationInfo(packageName,
- PackageManager.GET_UNINSTALLED_PACKAGES).uid;
+ PackageManager.MATCH_UNINSTALLED_PACKAGES).uid;
} catch (NameNotFoundException nnfe) {
return -1;
} finally {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 6c2e4d4..e88b72f 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2409,7 +2409,6 @@
case TYPE_WALLPAPER:
case TYPE_DREAM:
case TYPE_KEYGUARD_SCRIM:
- case TYPE_DOCK_DIVIDER:
return false;
default:
// Hide only windows below the keyguard host window.
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 0c429e5..144d7ac 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -19,6 +19,9 @@
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.HOME_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowState.RESIZE_HANDLE_WIDTH_IN_DP;
@@ -607,4 +610,39 @@
final TaskStack stack = mService.mStackIdToStack.get(DOCKED_STACK_ID);
return (stack != null && stack.isVisibleLocked()) ? stack : null;
}
+
+ /**
+ * Find the visible, touch-deliverable window under the given point
+ */
+ WindowState getTouchableWinAtPointLocked(float xf, float yf) {
+ WindowState touchedWin = null;
+ final int x = (int) xf;
+ final int y = (int) yf;
+
+ for (int i = mWindows.size() - 1; i >= 0; i--) {
+ WindowState window = mWindows.get(i);
+ final int flags = window.mAttrs.flags;
+ if (!window.isVisibleLw()) {
+ continue;
+ }
+ if ((flags & FLAG_NOT_TOUCHABLE) != 0) {
+ continue;
+ }
+
+ window.getVisibleBounds(mTmpRect);
+ if (!mTmpRect.contains(x, y)) {
+ continue;
+ }
+
+ window.getTouchableRegion(mTmpRegion);
+
+ final int touchFlags = flags & (FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL);
+ if (mTmpRegion.contains(x, y) || touchFlags == 0) {
+ touchedWin = window;
+ break;
+ }
+ }
+
+ return touchedWin;
+ }
}
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 412a455..75c06ff 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -27,6 +27,8 @@
import com.android.server.wm.DimLayer.DimLayerUser;
+import java.util.ArrayList;
+
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import static android.view.WindowManager.DOCKED_BOTTOM;
@@ -77,7 +79,17 @@
}
void setResizing(boolean resizing) {
- mResizing = resizing;
+ if (mResizing != resizing) {
+ mResizing = resizing;
+ resetDragResizingChangeReported();
+ }
+ }
+
+ private void resetDragResizingChangeReported() {
+ final WindowList windowList = mDisplayContent.getWindowList();
+ for (int i = windowList.size() - 1; i >= 0; i--) {
+ windowList.get(i).resetDragResizingChangeReported();
+ }
}
void setWindow(WindowState window) {
@@ -90,7 +102,9 @@
return;
}
TaskStack stack = mDisplayContent.mService.mStackIdToStack.get(DOCKED_STACK_ID);
- final boolean visible = stack != null && stack.isVisibleLocked();
+
+ // If the stack is invisible, we policy force hide it in WindowAnimator.shouldForceHide
+ final boolean visible = stack != null;
if (mLastVisibility == visible && !force) {
return;
}
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 9a3aaa5..cf27b97 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -27,8 +27,6 @@
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.Region;
import android.hardware.input.InputManager;
import android.os.IBinder;
import android.os.Message;
@@ -71,6 +69,13 @@
class DragState {
private static final long ANIMATION_DURATION_MS = 500;
+ private static final int DRAG_FLAGS_URI_ACCESS = View.DRAG_FLAG_GLOBAL_URI_READ |
+ View.DRAG_FLAG_GLOBAL_URI_WRITE;
+
+ private static final int DRAG_FLAGS_URI_PERMISSIONS = DRAG_FLAGS_URI_ACCESS |
+ View.DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION |
+ View.DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION;
+
final WindowManagerService mService;
IBinder mToken;
SurfaceControl mSurfaceControl;
@@ -95,10 +100,7 @@
WindowState mTargetWindow;
ArrayList<WindowState> mNotifiedWindows;
boolean mDragInProgress;
- Display mDisplay;
-
- private final Region mTmpRegion = new Region();
- private final Rect mTmpRect = new Rect();
+ DisplayContent mDisplayContent;
private Animation mAnimation;
final Transformation mTransformation = new Transformation();
@@ -131,11 +133,12 @@
* @param display The Display that the window being dragged is on.
*/
void register(Display display) {
- mDisplay = display;
if (DEBUG_DRAG) Slog.d(TAG_WM, "registering drag input channel");
if (mClientChannel != null) {
Slog.e(TAG_WM, "Duplicate register of drag input channel");
} else {
+ mDisplayContent = mService.getDisplayContentLocked(display.getDisplayId());
+
InputChannel[] channels = InputChannel.openInputChannelPair("drag");
mServerChannel = channels[0];
mClientChannel = channels[1];
@@ -149,7 +152,7 @@
WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null,
- mDisplay.getDisplayId());
+ display.getDisplayId());
mDragWindowHandle.name = "drag";
mDragWindowHandle.inputChannel = mServerChannel;
mDragWindowHandle.layer = getDragLayerLw();
@@ -174,7 +177,7 @@
mDragWindowHandle.frameLeft = 0;
mDragWindowHandle.frameTop = 0;
Point p = new Point();
- mDisplay.getRealSize(p);
+ display.getRealSize(p);
mDragWindowHandle.frameRight = p.x;
mDragWindowHandle.frameBottom = p.y;
@@ -244,12 +247,10 @@
Slog.d(TAG_WM, "broadcasting DRAG_STARTED at (" + touchX + ", " + touchY + ")");
}
- final WindowList windows = mService.getWindowListLocked(mDisplay);
- if (windows != null) {
- final int N = windows.size();
- for (int i = 0; i < N; i++) {
- sendDragStartedLw(windows.get(i), touchX, touchY, mDataDescription);
- }
+ final WindowList windows = mDisplayContent.getWindowList();
+ final int N = windows.size();
+ for (int i = 0; i < N; i++) {
+ sendDragStartedLw(windows.get(i), touchX, touchY, mDataDescription);
}
}
@@ -379,7 +380,7 @@
private void cleanUpDragLw() {
broadcastDragEndedLw();
if (isFromSource(InputDevice.SOURCE_MOUSE)) {
- mService.restorePointerIconLocked(mDisplay, mCurrentX, mCurrentY);
+ mService.restorePointerIconLocked(mDisplayContent, mCurrentX, mCurrentY);
}
// stop intercepting input
@@ -418,7 +419,7 @@
void notifyLocationLw(float x, float y) {
// Tell the affected window
- WindowState touchedWin = mService.getTouchableWinAtPointLocked(mDisplay, x, y);
+ WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y);
if (touchedWin == null) {
if (DEBUG_DRAG) Slog.d(TAG_WM, "No touched win at x=" + x + " y=" + y);
return;
@@ -463,17 +464,18 @@
mTargetWindow = touchedWin;
}
- // Tell the drop target about the data. Returns 'true' if we can immediately
+ // Find the drop target and tell it about the data. Returns 'true' if we can immediately
// dispatch the global drag-ended message, 'false' if we need to wait for a
// result from the recipient.
- boolean notifyDropLw(WindowState touchedWin, IDropPermissions dropPermissions,
- float x, float y) {
+ boolean notifyDropLw(float x, float y) {
if (mAnimation != null) {
return false;
}
mCurrentX = x;
mCurrentY = y;
+ WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y);
+
if (!isWindowNotified(touchedWin)) {
// "drop" outside a valid window -- no recipient to apply a
// timeout to, and we can send the drag-ended message immediately.
@@ -484,7 +486,21 @@
if (DEBUG_DRAG) {
Slog.d(TAG_WM, "sending DROP to " + touchedWin);
}
- if (mSourceUserId != UserHandle.getUserId(touchedWin.getOwningUid())){
+
+ final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid());
+
+ DropPermissionsHandler dropPermissions = null;
+ if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 &&
+ (mFlags & DRAG_FLAGS_URI_ACCESS) != 0) {
+ dropPermissions = new DropPermissionsHandler(
+ mData,
+ mUid,
+ touchedWin.getOwningPackage(),
+ mFlags & DRAG_FLAGS_URI_PERMISSIONS,
+ mSourceUserId,
+ targetUserId);
+ }
+ if (mSourceUserId != targetUserId){
mData.fixUris(mSourceUserId);
}
final int myPid = Process.myPid();
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 4167ac4..fe55e80 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -536,7 +536,20 @@
}
void setDragResizing(boolean dragResizing) {
- mDragResizing = dragResizing;
+ if (mDragResizing != dragResizing) {
+ mDragResizing = dragResizing;
+ resetDragResizingChangeReported();
+ }
+ }
+
+ void resetDragResizingChangeReported() {
+ for (int activityNdx = mAppTokens.size() - 1; activityNdx >= 0; --activityNdx) {
+ final ArrayList<WindowState> windows = mAppTokens.get(activityNdx).allAppWindows;
+ for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
+ final WindowState win = windows.get(winNdx);
+ win.resetDragResizingChangeReported();
+ }
+ }
}
boolean isDragResizing() {
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 7244676..8409058 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -898,7 +898,8 @@
}
boolean isVisibleLocked() {
- final boolean keyguardOn = mService.mPolicy.isKeyguardShowingOrOccluded();
+ final boolean keyguardOn = mService.mPolicy.isKeyguardShowingOrOccluded()
+ && !mService.mAnimator.mKeyguardGoingAway;
if (keyguardOn && !StackId.isAllowedOverLockscreen(mStackId)) {
// The keyguard is showing and the stack shouldn't show on top of the keyguard.
return false;
@@ -919,6 +920,16 @@
return mDragResizing;
}
+ private void setDragResizingLocked(boolean resizing) {
+ if (mDragResizing == resizing) {
+ return;
+ }
+ mDragResizing = resizing;
+ for (int i = mTasks.size() - 1; i >= 0 ; i--) {
+ mTasks.get(i).resetDragResizingChangeReported();
+ }
+ }
+
@Override // AnimatesBounds
public boolean setSize(Rect bounds) {
synchronized (mService.mWindowMap) {
@@ -936,14 +947,14 @@
@Override // AnimatesBounds
public void onAnimationStart() {
synchronized (mService.mWindowMap) {
- mDragResizing = true;
+ setDragResizingLocked(true);
}
}
@Override // AnimatesBounds
public void onAnimationEnd() {
synchronized (mService.mWindowMap) {
- mDragResizing = false;
+ setDragResizingLocked(false);
mService.requestTraversal();
}
if (mStackId == PINNED_STACK_ID) {
@@ -968,4 +979,4 @@
public void getFullScreenBounds(Rect bounds) {
getDisplayContent().getContentRect(bounds);
}
-}
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 8d2fb9b..f8a4d33 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -21,6 +21,7 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_KEYGUARD;
@@ -231,7 +232,10 @@
// Only hide windows if the keyguard is active and not animating away.
boolean keyguardOn = mPolicy.isKeyguardShowingOrOccluded()
&& mForceHiding != KEYGUARD_ANIMATING_OUT;
- return keyguardOn && !allowWhenLocked && (win.getDisplayId() == Display.DEFAULT_DISPLAY);
+ boolean hideDockDivider = win.mAttrs.type == TYPE_DOCK_DIVIDER
+ && win.getDisplayContent().getDockedStackLocked() == null;
+ return keyguardOn && !allowWhenLocked && (win.getDisplayId() == Display.DEFAULT_DISPLAY)
+ || hideDockDivider;
}
private void updateWindowsLocked(final int displayId) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c8f5dda..7169375 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -664,13 +664,6 @@
private WindowContentFrameStats mTempWindowRenderStats;
- private static final int DRAG_FLAGS_URI_ACCESS = View.DRAG_FLAG_GLOBAL_URI_READ |
- View.DRAG_FLAG_GLOBAL_URI_WRITE;
-
- private static final int DRAG_FLAGS_URI_PERMISSIONS = DRAG_FLAGS_URI_ACCESS |
- View.DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION |
- View.DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION;
-
final class DragInputEventReceiver extends InputEventReceiver {
// Set, if stylus button was down at the start of the drag.
private boolean mStylusButtonDownAtStart;
@@ -716,7 +709,7 @@
if (DEBUG_DRAG) Slog.d(TAG_WM, "Button no longer pressed; dropping at "
+ newX + "," + newY);
synchronized (mWindowMap) {
- endDrag = completeDropLw(newX, newY);
+ endDrag = mDragState.notifyDropLw(newX, newY);
}
} else {
synchronized (mWindowMap) {
@@ -730,7 +723,7 @@
if (DEBUG_DRAG) Slog.d(TAG_WM, "Got UP on move channel; dropping at "
+ newX + "," + newY);
synchronized (mWindowMap) {
- endDrag = completeDropLw(newX, newY);
+ endDrag = mDragState.notifyDropLw(newX, newY);
}
} break;
@@ -760,25 +753,6 @@
}
}
- private boolean completeDropLw(float x, float y) {
- WindowState dropTargetWin = getTouchableWinAtPointLocked(mDragState.mDisplay, x, y);
-
- DropPermissionsHandler dropPermissions = null;
- if (dropTargetWin != null &&
- (mDragState.mFlags & View.DRAG_FLAG_GLOBAL) != 0 &&
- (mDragState.mFlags & DRAG_FLAGS_URI_ACCESS) != 0) {
- dropPermissions = new DropPermissionsHandler(
- mDragState.mData,
- mDragState.mUid,
- dropTargetWin.getOwningPackage(),
- mDragState.mFlags & DRAG_FLAGS_URI_PERMISSIONS,
- mDragState.mSourceUserId,
- UserHandle.getUserId(dropTargetWin.getOwningUid()));
- }
-
- return mDragState.notifyDropLw(dropTargetWin, dropPermissions, x, y);
- }
-
/**
* Whether the UI is currently running in touch mode (not showing
* navigational focus because the user is directly pressing the screen).
@@ -8839,7 +8813,8 @@
Slog.v(TAG_WM, "Win " + w + " config changed: "
+ mCurConfiguration);
}
- final boolean dragResizingChanged = w.isDragResizeChanged();
+ final boolean dragResizingChanged = w.isDragResizeChanged()
+ && !w.isDragResizingChangeReported();
if (localLOGV) Slog.v(TAG_WM, "Resizing " + w
+ ": configChanged=" + configChanged
+ " dragResizingChanged=" + dragResizingChanged
@@ -10466,43 +10441,6 @@
}
}
- /**
- * Find the visible, touch-deliverable window under the given point
- */
- WindowState getTouchableWinAtPointLocked(Display display, float xf, float yf) {
- WindowState touchedWin = null;
- final int x = (int) xf;
- final int y = (int) yf;
-
- final WindowList windows = getWindowListLocked(display);
- if (windows == null) {
- return null;
- }
- final int N = windows.size();
- for (int i = N - 1; i >= 0; i--) {
- WindowState child = windows.get(i);
- final int flags = child.mAttrs.flags;
- if (!child.isVisibleLw()) {
- continue;
- }
- if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) {
- continue;
- }
-
- child.getTouchableRegion(mTmpRegion);
-
- final int touchFlags = flags &
- (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
- if (mTmpRegion.contains(x, y) || touchFlags == 0) {
- touchedWin = child;
- break;
- }
- }
-
- return touchedWin;
- }
-
private MousePositionTracker mMousePositionTracker = new MousePositionTracker();
private static class MousePositionTracker implements PointerEventListener {
@@ -10555,8 +10493,8 @@
if (displayContent == null) {
return;
}
- Display display = displayContent.getDisplay();
- WindowState windowUnderPointer = getTouchableWinAtPointLocked(display, mouseX, mouseY);
+ WindowState windowUnderPointer =
+ displayContent.getTouchableWinAtPointLocked(mouseX, mouseY);
if (windowUnderPointer != callingWin) {
return;
}
@@ -10570,11 +10508,12 @@
}
}
- void restorePointerIconLocked(Display display, float latestX, float latestY) {
+ void restorePointerIconLocked(DisplayContent displayContent, float latestX, float latestY) {
// Mouse position tracker has not been getting updates while dragging, update it now.
mMousePositionTracker.updatePosition(latestX, latestY);
- WindowState windowUnderPointer = getTouchableWinAtPointLocked(display, latestX, latestY);
+ WindowState windowUnderPointer =
+ displayContent.getTouchableWinAtPointLocked(latestX, latestY);
if (windowUnderPointer != null) {
try {
windowUnderPointer.mClient.updatePointerIcon(
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 37c8a7e..bea333b 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -161,6 +161,7 @@
boolean mAttachedHidden; // is our parent window hidden?
boolean mWallpaperVisible; // for wallpaper, what was last vis report?
boolean mDragResizing;
+ boolean mDragResizingChangeReported;
int mResizeMode;
RemoteCallbackList<IWindowFocusObserver> mFocusCallbacks;
@@ -2103,6 +2104,7 @@
mClient.resized(frame, overscanInsets, contentInsets, visibleInsets, stableInsets, outsets,
reportDraw, newConfig, getBackdropFrame(frame),
isDragResizeChanged() /* forceRelayout */);
+ mDragResizingChangeReported = true;
}
public void registerFocusObserver(IWindowFocusObserver observer) {
@@ -2137,6 +2139,20 @@
return mDragResizing != computeDragResizing();
}
+ /**
+ * @return Whether we reported a drag resize change to the application or not already.
+ */
+ boolean isDragResizingChangeReported() {
+ return mDragResizingChangeReported;
+ }
+
+ /**
+ * Resets the state whether we reported a drag resize change to the app.
+ */
+ void resetDragResizingChangeReported() {
+ mDragResizingChangeReported = false;
+ }
+
int getResizeMode() {
return mResizeMode;
}
@@ -2161,7 +2177,11 @@
}
void setDragResizing() {
- mDragResizing = computeDragResizing();
+ final boolean resizing = computeDragResizing();
+ if (resizing == mDragResizing) {
+ return;
+ }
+ mDragResizing = resizing;
mResizeMode = mDragResizing && mDisplayContent.mDividerControllerLocked.isResizing()
? DRAG_RESIZE_MODE_DOCKED_DIVIDER
: DRAG_RESIZE_MODE_FREEFORM;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index fa727d4..6ec0ba1 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2702,6 +2702,10 @@
if (info == null) {
throw new IllegalArgumentException("Bad admin: " + adminReceiver);
}
+ if (!info.getActivityInfo().applicationInfo.isInternal()) {
+ throw new IllegalArgumentException("Only apps in internal storage can be active admin: "
+ + adminReceiver);
+ }
synchronized (this) {
long ident = mInjector.binderClearCallingIdentity();
try {
@@ -5392,6 +5396,9 @@
}
synchronized (this) {
enforceCanSetDeviceOwnerLocked(userId);
+ if (getActiveAdminUncheckedLocked(admin, userId) == null) {
+ throw new IllegalArgumentException("Not active admin: " + admin);
+ }
// Shutting down backup manager service permanently.
long ident = mInjector.binderClearCallingIdentity();
@@ -5567,6 +5574,11 @@
}
synchronized (this) {
enforceCanSetProfileOwnerLocked(userHandle);
+
+ if (getActiveAdminUncheckedLocked(who, userHandle) == null) {
+ throw new IllegalArgumentException("Not active admin: " + who);
+ }
+
mOwners.setProfileOwner(who, ownerName, userHandle);
mOwners.writeProfileOwner(userHandle);
return true;
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java
index 38551b6..5229b40 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/services/net/java/android/net/dhcp/DhcpClient.java
@@ -25,7 +25,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.net.BaseDhcpStateMachine;
import android.net.DhcpResults;
import android.net.InterfaceConfiguration;
import android.net.LinkAddress;
@@ -83,7 +82,7 @@
*
* @hide
*/
-public class DhcpClient extends BaseDhcpStateMachine {
+public class DhcpClient extends StateMachine {
private static final String TAG = "DhcpClient";
private static final boolean DBG = true;
@@ -240,16 +239,10 @@
mOneshotTimeoutAlarm = makeWakeupMessage("ONESHOT_TIMEOUT", CMD_ONESHOT_TIMEOUT);
}
- @Override
public void registerForPreDhcpNotification() {
mRegisteredForPreDhcpNotification = true;
}
- public static BaseDhcpStateMachine makeDhcpStateMachine(
- Context context, StateMachine controller, String intf) {
- return makeDhcpClient(context, controller, intf);
- }
-
public static DhcpClient makeDhcpClient(
Context context, StateMachine controller, String intf) {
DhcpClient client = new DhcpClient(context, controller, intf);
@@ -446,12 +439,12 @@
*
* @hide
*/
- @Override
public void doQuit() {
Log.d(TAG, "doQuit");
quit();
}
+ @Override
protected void onQuitting() {
Log.d(TAG, "onQuitting");
mController.sendMessage(CMD_ON_QUIT);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index b23ad50..6d168b0 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -1668,7 +1668,12 @@
// that the test user is not affiliated anymore.
dpm.clearProfileOwner(admin2);
final ComponentName admin = new ComponentName("test", "test");
- markPackageAsInstalled(admin.getPackageName(), null, DpmMockContext.CALLER_USER_HANDLE);
+
+ setUpPackageManagerForFakeAdmin(admin, DpmMockContext.CALLER_UID,
+ /* enabledSetting =*/ PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ /* appTargetSdk = */ null, admin2);
+
+ dpm.setActiveAdmin(admin, /* refreshing =*/ true, DpmMockContext.CALLER_USER_HANDLE);
assertTrue(dpm.setProfileOwner(admin, "owner-name", DpmMockContext.CALLER_USER_HANDLE));
assertFalse(dpm.isAffiliatedUser());
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
index 53ca45d..ca43644 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
@@ -19,6 +19,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -96,12 +97,26 @@
protected void setUpPackageManagerForAdmin(ComponentName admin, int packageUid,
Integer enabledSetting, Integer appTargetSdk) throws Exception {
+ setUpPackageManagerForFakeAdmin(admin, packageUid, enabledSetting, appTargetSdk,
+ admin);
+ }
+
+ /**
+ * Set up a component in the mock package manager to be an active admin.
+ *
+ * @param admin ComponentName that's visible to the test code, which doesn't have to exist.
+ * @param copyFromAdmin package information for {@code admin} will be built based on this
+ * component's information.
+ */
+ protected void setUpPackageManagerForFakeAdmin(ComponentName admin, int packageUid,
+ Integer enabledSetting, Integer appTargetSdk, ComponentName copyFromAdmin)
+ throws Exception {
// Set up getApplicationInfo().
final ApplicationInfo ai = DpmTestUtils.cloneParcelable(
mRealTestContext.getPackageManager().getApplicationInfo(
- admin.getPackageName(),
+ copyFromAdmin.getPackageName(),
PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS));
ai.enabledSetting = enabledSetting == null
@@ -111,6 +126,8 @@
ai.targetSdkVersion = appTargetSdk;
}
ai.uid = packageUid;
+ ai.packageName = admin.getPackageName();
+ ai.name = admin.getClassName();
doReturn(ai).when(mMockContext.ipackageManager).getApplicationInfo(
eq(admin.getPackageName()),
@@ -120,7 +137,7 @@
// Set up queryBroadcastReceivers().
final Intent resolveIntent = new Intent();
- resolveIntent.setComponent(admin);
+ resolveIntent.setComponent(copyFromAdmin);
final List<ResolveInfo> realResolveInfo =
mRealTestContext.getPackageManager().queryBroadcastReceivers(
resolveIntent,
@@ -132,7 +149,10 @@
realResolveInfo.set(0, DpmTestUtils.cloneParcelable(realResolveInfo.get(0)));
// We need to rewrite the UID in the activity info.
- realResolveInfo.get(0).activityInfo.applicationInfo = ai;
+ final ActivityInfo aci = realResolveInfo.get(0).activityInfo;
+ aci.applicationInfo = ai;
+ aci.packageName = admin.getPackageName();
+ aci.name = admin.getClassName();
doReturn(realResolveInfo).when(mMockContext.packageManager).queryBroadcastReceiversAsUser(
MockUtils.checkIntentComponent(admin),
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 46ad8a1..8e891bf 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -74,6 +74,7 @@
import com.android.internal.app.IBatteryStats;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.SomeArgs;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.SystemService;
@@ -125,6 +126,7 @@
Handler mHandler;
AppOpsManager mAppOps;
UserManager mUserManager;
+ PackageManager mPackageManager;
AppWidgetManager mAppWidgetManager;
IDeviceIdleController mDeviceIdleController;
private DisplayManager mDisplayManager;
@@ -157,7 +159,7 @@
public void onStart() {
mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
-
+ mPackageManager = getContext().getPackageManager();
mHandler = new H(BackgroundThread.get().getLooper());
File systemDataDir = new File(Environment.getDataDirectory(), "system");
@@ -296,9 +298,8 @@
private void initializeDefaultsForSystemApps(int userId) {
Slog.d(TAG, "Initializing defaults for system apps on user " + userId);
final long elapsedRealtime = SystemClock.elapsedRealtime();
- List<PackageInfo> packages = getContext().getPackageManager().getInstalledPackagesAsUser(
- PackageManager.MATCH_DISABLED_COMPONENTS
- | PackageManager.MATCH_UNINSTALLED_PACKAGES,
+ List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
+ PackageManager.MATCH_DISABLED_COMPONENTS,
userId);
final int packageCount = packages.size();
for (int i = 0; i < packageCount; i++) {
@@ -398,47 +399,59 @@
}
}
- /** Check all running users' or specified user's apps to see if they enter an idle state. */
- void checkIdleStates(int checkUserId) {
+ /**
+ * Check all running users' or specified user's apps to see if they enter an idle state.
+ * @return Returns whether checking should continue periodically.
+ */
+ boolean checkIdleStates(int checkUserId) {
if (!mAppIdleEnabled) {
- return;
+ return false;
}
- final int[] userIds;
+ final int[] runningUserIds;
try {
- if (checkUserId == UserHandle.USER_ALL) {
- userIds = ActivityManagerNative.getDefault().getRunningUserIds();
- } else {
- userIds = new int[] { checkUserId };
+ runningUserIds = ActivityManagerNative.getDefault().getRunningUserIds();
+ if (checkUserId != UserHandle.USER_ALL
+ && !ArrayUtils.contains(runningUserIds, checkUserId)) {
+ return false;
}
} catch (RemoteException re) {
- return;
+ return false;
}
final long elapsedRealtime = SystemClock.elapsedRealtime();
- for (int i = 0; i < userIds.length; i++) {
- final int userId = userIds[i];
- List<PackageInfo> packages =
- getContext().getPackageManager().getInstalledPackagesAsUser(
- PackageManager.MATCH_DISABLED_COMPONENTS
- | PackageManager.MATCH_UNINSTALLED_PACKAGES,
- userId);
- synchronized (mLock) {
- final int packageCount = packages.size();
- for (int p = 0; p < packageCount; p++) {
- final PackageInfo pi = packages.get(p);
- final String packageName = pi.packageName;
- final boolean isIdle = isAppIdleFiltered(packageName,
- UserHandle.getAppId(pi.applicationInfo.uid),
- userId, elapsedRealtime);
- mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
- userId, isIdle ? 1 : 0, packageName));
- if (isIdle) {
+ for (int i = 0; i < runningUserIds.length; i++) {
+ final int userId = runningUserIds[i];
+ if (checkUserId != UserHandle.USER_ALL && checkUserId != userId) {
+ continue;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "Checking idle state for user " + userId);
+ }
+ List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
+ PackageManager.MATCH_DISABLED_COMPONENTS,
+ userId);
+ final int packageCount = packages.size();
+ for (int p = 0; p < packageCount; p++) {
+ final PackageInfo pi = packages.get(p);
+ final String packageName = pi.packageName;
+ final boolean isIdle = isAppIdleFiltered(packageName,
+ UserHandle.getAppId(pi.applicationInfo.uid),
+ userId, elapsedRealtime);
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
+ userId, isIdle ? 1 : 0, packageName));
+ if (isIdle) {
+ synchronized (mLock) {
mAppIdleHistory.setIdle(packageName, userId, elapsedRealtime);
}
}
}
}
+ if (DEBUG) {
+ Slog.d(TAG, "checkIdleStates took "
+ + (SystemClock.elapsedRealtime() - elapsedRealtime));
+ }
+ return true;
}
/** Check if it's been a while since last parole and let idle apps do some work */
@@ -459,7 +472,7 @@
private void notifyBatteryStats(String packageName, int userId, boolean idle) {
try {
- final int uid = AppGlobals.getPackageManager().getPackageUid(packageName,
+ final int uid = mPackageManager.getPackageUidAsUser(packageName,
PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
if (idle) {
mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE,
@@ -468,7 +481,7 @@
mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE,
packageName, uid);
}
- } catch (RemoteException re) {
+ } catch (NameNotFoundException | RemoteException e) {
}
}
@@ -592,7 +605,7 @@
// Only force the sync adapters to active if the provider is not in the same package and
// the sync adapter is a system package.
try {
- PackageInfo pi = AppGlobals.getPackageManager().getPackageInfo(
+ PackageInfo pi = mPackageManager.getPackageInfoAsUser(
packageName, PackageManager.MATCH_SYSTEM_ONLY, userId);
if (pi == null || pi.applicationInfo == null) {
continue;
@@ -600,7 +613,7 @@
if (!packageName.equals(providerPkgName)) {
forceIdleState(packageName, userId, false);
}
- } catch (RemoteException re) {
+ } catch (NameNotFoundException e) {
// Shouldn't happen
}
}
@@ -725,7 +738,7 @@
int getAppId(String packageName) {
try {
- ApplicationInfo ai = getContext().getPackageManager().getApplicationInfo(packageName,
+ ApplicationInfo ai = mPackageManager.getApplicationInfo(packageName,
PackageManager.MATCH_UNINSTALLED_PACKAGES
| PackageManager.MATCH_DISABLED_COMPONENTS);
return ai.uid;
@@ -772,12 +785,8 @@
}
} catch (RemoteException re) {
}
- // TODO: Optimize this check
- if (isActiveDeviceAdmin(packageName, userId)) {
- return false;
- }
- if (isCarrierApp(packageName)) {
+ if (isActiveDeviceAdmin(packageName, userId)) {
return false;
}
@@ -790,7 +799,17 @@
return false;
}
- return isAppIdleUnfiltered(packageName, userId, elapsedRealtime);
+ if (!isAppIdleUnfiltered(packageName, userId, elapsedRealtime)) {
+ return false;
+ }
+
+ // Check this last, as it is the most expensive check
+ // TODO: Optimize this by fetching the carrier privileged apps ahead of time
+ if (isCarrierApp(packageName)) {
+ return false;
+ }
+
+ return true;
}
int[] getIdleUidsForUser(int userId) {
@@ -803,7 +822,7 @@
List<ApplicationInfo> apps;
try {
ParceledListSlice<ApplicationInfo> slice = AppGlobals.getPackageManager()
- .getInstalledApplications(PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
+ .getInstalledApplications(/* flags= */ 0, userId);
if (slice == null) {
return new int[0];
}
@@ -833,7 +852,9 @@
uidStates.setValueAt(index, value + 1 + (idle ? 1<<16 : 0));
}
}
-
+ if (DEBUG) {
+ Slog.d(TAG, "getIdleUids took " + (SystemClock.elapsedRealtime() - elapsedRealtime));
+ }
int numIdle = 0;
for (int i = uidStates.size() - 1; i >= 0; i--) {
int value = uidStates.valueAt(i);
@@ -865,15 +886,7 @@
private boolean isActiveDeviceAdmin(String packageName, int userId) {
DevicePolicyManager dpm = getContext().getSystemService(DevicePolicyManager.class);
if (dpm == null) return false;
- List<ComponentName> components = dpm.getActiveAdminsAsUser(userId);
- if (components == null) return false;
- final int size = components.size();
- for (int i = 0; i < size; i++) {
- if (components.get(i).getPackageName().equals(packageName)) {
- return true;
- }
- }
- return false;
+ return dpm.packageHasActiveAdmins(packageName, userId);
}
private boolean isCarrierApp(String packageName) {
@@ -1011,10 +1024,11 @@
break;
case MSG_CHECK_IDLE_STATES:
- checkIdleStates(msg.arg1);
- mHandler.sendMessageDelayed(mHandler.obtainMessage(
- MSG_CHECK_IDLE_STATES, msg.arg1, 0),
- mCheckIdleIntervalMillis);
+ if (checkIdleStates(msg.arg1)) {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(
+ MSG_CHECK_IDLE_STATES, msg.arg1, 0),
+ mCheckIdleIntervalMillis);
+ }
break;
case MSG_ONE_TIME_CHECK_IDLE_STATES:
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java
index 18a5d59..f7cd6a3 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java
@@ -54,6 +54,7 @@
private static final String CREATE_TABLE_ST_SOUND_MODEL = "CREATE TABLE "
+ GenericSoundModelContract.TABLE + "("
+ GenericSoundModelContract.KEY_MODEL_UUID + " TEXT PRIMARY KEY,"
+ + GenericSoundModelContract.KEY_VENDOR_UUID + " TEXT,"
+ GenericSoundModelContract.KEY_DATA + " BLOB" + " )";
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 354075e..cde47bd 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -16,12 +16,16 @@
package com.android.server.soundtrigger;
+import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
import android.hardware.soundtrigger.SoundTrigger;
+import android.hardware.soundtrigger.SoundTrigger.GenericRecognitionEvent;
+import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent;
import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra;
@@ -29,6 +33,7 @@
import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
+import android.hardware.soundtrigger.SoundTrigger.SoundModel;
import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent;
import android.hardware.soundtrigger.SoundTriggerModule;
import android.os.PowerManager;
@@ -40,9 +45,16 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.UUID;
/**
- * Helper for {@link SoundTrigger} APIs.
+ * Helper for {@link SoundTrigger} APIs. Supports two types of models:
+ * (i) A voice model which is exported via the {@link VoiceInteractionService}. There can only be
+ * a single voice model running on the DSP at any given time.
+ *
+ * (ii) Generic sound-trigger models: Supports multiple of these.
+ *
* Currently this just acts as an abstraction over all SoundTrigger API calls.
*
* @hide
@@ -62,7 +74,7 @@
private static final int INVALID_VALUE = Integer.MIN_VALUE;
/** The {@link ModuleProperties} for the system, or null if none exists. */
- final ModuleProperties moduleProperties;
+ final ModuleProperties mModuleProperties;
/** The properties for the DSP module */
private SoundTriggerModule mModule;
@@ -72,21 +84,36 @@
private final PhoneStateListener mPhoneStateListener;
private final PowerManager mPowerManager;
- // TODO: Since many layers currently only deal with one recognition
+ // TODO: Since the voice layer currently only handles one recognition
// we simplify things by assuming one listener here too.
- private IRecognitionStatusCallback mActiveListener;
+ private IRecognitionStatusCallback mKeyphraseListener;
+
+ // The SoundTriggerManager layer handles multiple generic recognition models. We store the
+ // ModelData here in a hashmap.
+ private final HashMap<UUID, ModelData> mGenericModelDataMap;
+
+ // Note: KeyphraseId is not really used.
private int mKeyphraseId = INVALID_VALUE;
- private int mCurrentSoundModelHandle = INVALID_VALUE;
+
+ // Current voice sound model handle. We only allow one voice model to run at any given time.
+ private int mCurrentKeyphraseModelHandle = INVALID_VALUE;
private KeyphraseSoundModel mCurrentSoundModel = null;
// FIXME: Ideally this should not be stored if allowMultipleTriggers happens at a lower layer.
private RecognitionConfig mRecognitionConfig = null;
+
+ // Whether we are requesting recognition to start.
private boolean mRequested = false;
private boolean mCallActive = false;
private boolean mIsPowerSaveMode = false;
// Indicates if the native sound trigger service is disabled or not.
// This is an indirect indication of the microphone being open in some other application.
private boolean mServiceDisabled = false;
- private boolean mStarted = false;
+
+ // Whether we have ANY recognition (keyphrase or generic) running.
+ private boolean mRecognitionRunning = false;
+
+ // Keeps track of whether the keyphrase recognition is running.
+ private boolean mKeyphraseStarted = false;
private boolean mRecognitionAborted = false;
private PowerSaveModeListener mPowerSaveModeListener;
@@ -96,14 +123,87 @@
mContext = context;
mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ mGenericModelDataMap = new HashMap<UUID, ModelData>();
mPhoneStateListener = new MyCallStateListener();
if (status != SoundTrigger.STATUS_OK || modules.size() == 0) {
Slog.w(TAG, "listModules status=" + status + ", # of modules=" + modules.size());
- moduleProperties = null;
+ mModuleProperties = null;
mModule = null;
} else {
// TODO: Figure out how to determine which module corresponds to the DSP hardware.
- moduleProperties = modules.get(0);
+ mModuleProperties = modules.get(0);
+ }
+ }
+
+ /**
+ * Starts recognition for the given generic sound model ID.
+ *
+ * @param soundModel The sound model to use for recognition.
+ * @param listener The listener for the recognition events related to the given keyphrase.
+ * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
+ */
+ int startGenericRecognition(UUID modelId, GenericSoundModel soundModel,
+ IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig) {
+ if (soundModel == null || callback == null || recognitionConfig == null) {
+ Slog.w(TAG, "Passed in bad data to startGenericRecognition().");
+ return STATUS_ERROR;
+ }
+
+ synchronized (mLock) {
+
+ if (mModuleProperties == null) {
+ Slog.w(TAG, "Attempting startRecognition without the capability");
+ return STATUS_ERROR;
+ }
+
+ if (mModule == null) {
+ mModule = SoundTrigger.attachModule(mModuleProperties.id, this, null);
+ if (mModule == null) {
+ Slog.w(TAG, "startRecognition cannot attach to sound trigger module");
+ return STATUS_ERROR;
+ }
+ }
+
+ // Initialize power save, call active state monitoring logic.
+ if (!mRecognitionRunning) {
+ initializeTelephonyAndPowerStateListeners();
+ }
+
+ // Fetch a ModelData instance from the hash map. Creates a new one if none
+ // exists.
+ ModelData modelData = getOrCreateGenericModelData(modelId);
+
+ IRecognitionStatusCallback oldCallback = modelData.getCallback();
+ if (oldCallback != null) {
+ Slog.w(TAG, "Canceling previous recognition for model id: " + modelId);
+ try {
+ oldCallback.onError(STATUS_ERROR);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "RemoteException in onDetectionStopped", e);
+ }
+ modelData.clearCallback();
+ }
+
+ // Load the model if its not loaded.
+ if (!modelData.isModelLoaded()) {
+ // Load the model
+ int[] handle = new int[] { INVALID_VALUE };
+ int status = mModule.loadSoundModel(soundModel, handle);
+ if (status != SoundTrigger.STATUS_OK) {
+ Slog.w(TAG, "loadSoundModel call failed with " + status);
+ return status;
+ }
+ if (handle[0] == INVALID_VALUE) {
+ Slog.w(TAG, "loadSoundModel call returned invalid sound model handle");
+ return STATUS_ERROR;
+ }
+ modelData.setHandle(handle[0]);
+ }
+ modelData.setCallback(callback);
+ modelData.setRecognitionConfig(recognitionConfig);
+
+ // Don't notify for synchronous calls.
+ return startGenericRecognitionLocked(modelData, false);
}
}
@@ -116,7 +216,7 @@
* @param listener The listener for the recognition events related to the given keyphrase.
* @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
*/
- int startRecognition(int keyphraseId,
+ int startKeyphraseRecognition(int keyphraseId,
KeyphraseSoundModel soundModel,
IRecognitionStatusCallback listener,
RecognitionConfig recognitionConfig) {
@@ -129,36 +229,24 @@
Slog.d(TAG, "startRecognition for keyphraseId=" + keyphraseId
+ " soundModel=" + soundModel + ", listener=" + listener.asBinder()
+ ", recognitionConfig=" + recognitionConfig);
- Slog.d(TAG, "moduleProperties=" + moduleProperties);
+ Slog.d(TAG, "moduleProperties=" + mModuleProperties);
Slog.d(TAG, "current listener="
- + (mActiveListener == null ? "null" : mActiveListener.asBinder()));
- Slog.d(TAG, "current SoundModel handle=" + mCurrentSoundModelHandle);
+ + (mKeyphraseListener == null ? "null" : mKeyphraseListener.asBinder()));
+ Slog.d(TAG, "current SoundModel handle=" + mCurrentKeyphraseModelHandle);
Slog.d(TAG, "current SoundModel UUID="
+ (mCurrentSoundModel == null ? null : mCurrentSoundModel.uuid));
}
- if (!mStarted) {
- // Get the current call state synchronously for the first recognition.
- mCallActive = mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE;
- // Register for call state changes when the first call to start recognition occurs.
- mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
-
- // Register for power saver mode changes when the first call to start recognition
- // occurs.
- if (mPowerSaveModeListener == null) {
- mPowerSaveModeListener = new PowerSaveModeListener();
- mContext.registerReceiver(mPowerSaveModeListener,
- new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
- }
- mIsPowerSaveMode = mPowerManager.isPowerSaveMode();
+ if (!mRecognitionRunning) {
+ initializeTelephonyAndPowerStateListeners();
}
- if (moduleProperties == null) {
+ if (mModuleProperties == null) {
Slog.w(TAG, "Attempting startRecognition without the capability");
return STATUS_ERROR;
}
if (mModule == null) {
- mModule = SoundTrigger.attachModule(moduleProperties.id, this, null);
+ mModule = SoundTrigger.attachModule(mModuleProperties.id, this, null);
if (mModule == null) {
Slog.w(TAG, "startRecognition cannot attach to sound trigger module");
return STATUS_ERROR;
@@ -168,32 +256,32 @@
// Unload the previous model if the current one isn't invalid
// and, it's not the same as the new one.
// This helps use cache and reuse the model and just start/stop it when necessary.
- if (mCurrentSoundModelHandle != INVALID_VALUE
+ if (mCurrentKeyphraseModelHandle != INVALID_VALUE
&& !soundModel.equals(mCurrentSoundModel)) {
Slog.w(TAG, "Unloading previous sound model");
- int status = mModule.unloadSoundModel(mCurrentSoundModelHandle);
+ int status = mModule.unloadSoundModel(mCurrentKeyphraseModelHandle);
if (status != SoundTrigger.STATUS_OK) {
Slog.w(TAG, "unloadSoundModel call failed with " + status);
}
- internalClearSoundModelLocked();
- mStarted = false;
+ internalClearKeyphraseSoundModelLocked();
+ mKeyphraseStarted = false;
}
// If the previous recognition was by a different listener,
// Notify them that it was stopped.
- if (mActiveListener != null && mActiveListener.asBinder() != listener.asBinder()) {
+ if (mKeyphraseListener != null && mKeyphraseListener.asBinder() != listener.asBinder()) {
Slog.w(TAG, "Canceling previous recognition");
try {
- mActiveListener.onError(STATUS_ERROR);
+ mKeyphraseListener.onError(STATUS_ERROR);
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException in onDetectionStopped", e);
}
- mActiveListener = null;
+ mKeyphraseListener = null;
}
// Load the sound model if the current one is null.
- int soundModelHandle = mCurrentSoundModelHandle;
- if (mCurrentSoundModelHandle == INVALID_VALUE
+ int soundModelHandle = mCurrentKeyphraseModelHandle;
+ if (mCurrentKeyphraseModelHandle == INVALID_VALUE
|| mCurrentSoundModel == null) {
int[] handle = new int[] { INVALID_VALUE };
int status = mModule.loadSoundModel(soundModel, handle);
@@ -213,18 +301,81 @@
// Start the recognition.
mRequested = true;
mKeyphraseId = keyphraseId;
- mCurrentSoundModelHandle = soundModelHandle;
+ mCurrentKeyphraseModelHandle = soundModelHandle;
mCurrentSoundModel = soundModel;
mRecognitionConfig = recognitionConfig;
// Register the new listener. This replaces the old one.
// There can only be a maximum of one active listener at any given time.
- mActiveListener = listener;
+ mKeyphraseListener = listener;
return updateRecognitionLocked(false /* don't notify for synchronous calls */);
}
}
/**
+ * Stops recognition for the given generic sound model.
+ *
+ * @param modelId The identifier of the generic sound model for which
+ * the recognition is to be stopped.
+ * @param listener The listener for the recognition events related to the given sound model.
+ *
+ * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
+ */
+ int stopGenericRecognition(UUID modelId, IRecognitionStatusCallback listener) {
+ if (listener == null) {
+ return STATUS_ERROR;
+ }
+
+ synchronized (mLock) {
+ ModelData modelData = mGenericModelDataMap.get(modelId);
+ if (modelData == null) {
+ Slog.w(TAG, "Attempting stopRecognition on invalid model with id:" + modelId);
+ return STATUS_ERROR;
+ }
+
+ IRecognitionStatusCallback currentCallback = modelData.getCallback();
+ if (DBG) {
+ Slog.d(TAG, "stopRecognition for modelId=" + modelId
+ + ", listener=" + listener.asBinder());
+ Slog.d(TAG, "current callback ="
+ + (currentCallback == null ? "null" : currentCallback.asBinder()));
+ }
+
+ if (mModuleProperties == null || mModule == null) {
+ Slog.w(TAG, "Attempting stopRecognition without the capability");
+ return STATUS_ERROR;
+ }
+
+ if (currentCallback == null || !modelData.modelStarted()) {
+ // startRecognition hasn't been called or it failed.
+ Slog.w(TAG, "Attempting stopRecognition without a successful startRecognition");
+ return STATUS_ERROR;
+ }
+ if (currentCallback.asBinder() != listener.asBinder()) {
+ // We don't allow a different listener to stop the recognition than the one
+ // that started it.
+ Slog.w(TAG, "Attempting stopRecognition for another recognition");
+ return STATUS_ERROR;
+ }
+
+ int status = stopGenericRecognitionLocked(modelData, false /* don't notify for synchronous calls */);
+ if (status != SoundTrigger.STATUS_OK) {
+ return status;
+ }
+
+ // We leave the sound model loaded but not started, this helps us when we start
+ // back.
+ // Also clear the internal state once the recognition has been stopped.
+ modelData.clearState();
+ modelData.clearCallback();
+ if (!computeRecognitionRunning()) {
+ internalClearGlobalStateLocked();
+ }
+ return status;
+ }
+ }
+
+ /**
* Stops recognition for the given {@link Keyphrase} if a recognition is
* currently active.
*
@@ -234,7 +385,7 @@
*
* @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
*/
- int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
+ int stopKeyphraseRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
if (listener == null) {
return STATUS_ERROR;
}
@@ -244,20 +395,20 @@
Slog.d(TAG, "stopRecognition for keyphraseId=" + keyphraseId
+ ", listener=" + listener.asBinder());
Slog.d(TAG, "current listener="
- + (mActiveListener == null ? "null" : mActiveListener.asBinder()));
+ + (mKeyphraseListener == null ? "null" : mKeyphraseListener.asBinder()));
}
- if (moduleProperties == null || mModule == null) {
+ if (mModuleProperties == null || mModule == null) {
Slog.w(TAG, "Attempting stopRecognition without the capability");
return STATUS_ERROR;
}
- if (mActiveListener == null) {
+ if (mKeyphraseListener == null) {
// startRecognition hasn't been called or it failed.
Slog.w(TAG, "Attempting stopRecognition without a successful startRecognition");
return STATUS_ERROR;
}
- if (mActiveListener.asBinder() != listener.asBinder()) {
+ if (mKeyphraseListener.asBinder() != listener.asBinder()) {
// We don't allow a different listener to stop the recognition than the one
// that started it.
Slog.w(TAG, "Attempting stopRecognition for another recognition");
@@ -274,7 +425,8 @@
// We leave the sound model loaded but not started, this helps us when we start
// back.
// Also clear the internal state once the recognition has been stopped.
- internalClearStateLocked();
+ internalClearKeyphraseStateLocked();
+ internalClearGlobalStateLocked();
return status;
}
}
@@ -284,38 +436,56 @@
*/
void stopAllRecognitions() {
synchronized (mLock) {
- if (moduleProperties == null || mModule == null) {
+ if (mModuleProperties == null || mModule == null) {
return;
}
- if (mCurrentSoundModelHandle == INVALID_VALUE) {
- return;
+ // Stop Keyphrase recognition if one exists.
+ if (mCurrentKeyphraseModelHandle != INVALID_VALUE) {
+
+ mRequested = false;
+ int status = updateRecognitionLocked(
+ false /* don't notify for synchronous calls */);
+ internalClearKeyphraseStateLocked();
}
- mRequested = false;
- int status = updateRecognitionLocked(false /* don't notify for synchronous calls */);
- internalClearStateLocked();
+ // Stop all generic recognition models.
+ for (ModelData model : mGenericModelDataMap.values()) {
+ if (model.modelStarted()) {
+ int status = stopGenericRecognitionLocked(model,
+ false /* do not notify for synchronous calls */);
+ if (status != STATUS_OK) {
+ // What else can we do if there is an error here.
+ Slog.w(TAG, "Error stopping generic model: " + model.getHandle());
+ }
+ model.clearState();
+ model.clearCallback();
+ }
+ }
+ internalClearGlobalStateLocked();
}
}
public ModuleProperties getModuleProperties() {
- return moduleProperties;
+ return mModuleProperties;
}
//---- SoundTrigger.StatusListener methods
@Override
public void onRecognition(RecognitionEvent event) {
- if (event == null || !(event instanceof KeyphraseRecognitionEvent)) {
- Slog.w(TAG, "Invalid recognition event!");
+ if (event == null) {
+ Slog.w(TAG, "Null recognition event!");
+ return;
+ }
+
+ if (!(event instanceof KeyphraseRecognitionEvent) &&
+ !(event instanceof GenericRecognitionEvent)) {
+ Slog.w(TAG, "Invalid recognition event type (not one of generic or keyphrase) !");
return;
}
if (DBG) Slog.d(TAG, "onRecognition: " + event);
synchronized (mLock) {
- if (mActiveListener == null) {
- Slog.w(TAG, "received onRecognition event without any listener for it");
- return;
- }
switch (event.status) {
// Fire aborts/failures to all listeners since it's not tied to a keyphrase.
case SoundTrigger.RECOGNITION_STATUS_ABORT:
@@ -325,12 +495,60 @@
onRecognitionFailureLocked();
break;
case SoundTrigger.RECOGNITION_STATUS_SUCCESS:
- onRecognitionSuccessLocked((KeyphraseRecognitionEvent) event);
+
+ if (isKeyphraseRecognitionEvent(event)) {
+ onKeyphraseRecognitionSuccessLocked((KeyphraseRecognitionEvent) event);
+ } else {
+ onGenericRecognitionSuccessLocked((GenericRecognitionEvent) event);
+ }
+
break;
}
}
}
+ private boolean isKeyphraseRecognitionEvent(RecognitionEvent event) {
+ return mCurrentKeyphraseModelHandle == event.soundModelHandle;
+ }
+
+ private void onGenericRecognitionSuccessLocked(GenericRecognitionEvent event) {
+ if (event.status != SoundTrigger.RECOGNITION_STATUS_SUCCESS) {
+ return;
+ }
+ ModelData model = getModelDataFor(event.soundModelHandle);
+ if (model == null) {
+ Slog.w(TAG, "Generic recognition event: Model does not exist for handle: " +
+ event.soundModelHandle);
+ return;
+ }
+
+ IRecognitionStatusCallback callback = model.getCallback();
+ if (callback == null) {
+ Slog.w(TAG, "Generic recognition event: Null callback for model handle: " +
+ event.soundModelHandle);
+ return;
+ }
+
+ try {
+ callback.onDetected((GenericRecognitionEvent) event);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "RemoteException in onDetected", e);
+ }
+
+ model.setStopped();
+ RecognitionConfig config = model.getRecognitionConfig();
+ if (config == null) {
+ Slog.w(TAG, "Generic recognition event: Null RecognitionConfig for model handle: " +
+ event.soundModelHandle);
+ return;
+ }
+
+ // TODO: Remove this block if the lower layer supports multiple triggers.
+ if (config.allowMultipleTriggers) {
+ startGenericRecognitionLocked(model, true /* notify */);
+ }
+ }
+
@Override
public void onSoundModelUpdate(SoundModelEvent event) {
if (event == null) {
@@ -399,18 +617,25 @@
private void onRecognitionFailureLocked() {
Slog.w(TAG, "Recognition failure");
try {
- if (mActiveListener != null) {
- mActiveListener.onError(STATUS_ERROR);
+ if (mKeyphraseListener != null) {
+ mKeyphraseListener.onError(STATUS_ERROR);
}
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException in onError", e);
} finally {
- internalClearStateLocked();
+ internalClearKeyphraseStateLocked();
+ internalClearGlobalStateLocked();
}
}
- private void onRecognitionSuccessLocked(KeyphraseRecognitionEvent event) {
+ private void onKeyphraseRecognitionSuccessLocked(KeyphraseRecognitionEvent event) {
Slog.i(TAG, "Recognition success");
+
+ if (mKeyphraseListener == null) {
+ Slog.w(TAG, "received onRecognition event without any listener for it");
+ return;
+ }
+
KeyphraseRecognitionExtra[] keyphraseExtras =
((KeyphraseRecognitionEvent) event).keyphraseExtras;
if (keyphraseExtras == null || keyphraseExtras.length == 0) {
@@ -424,14 +649,14 @@
}
try {
- if (mActiveListener != null) {
- mActiveListener.onDetected((KeyphraseRecognitionEvent) event);
+ if (mKeyphraseListener != null) {
+ mKeyphraseListener.onDetected((KeyphraseRecognitionEvent) event);
}
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException in onDetected", e);
}
- mStarted = false;
+ mKeyphraseStarted = false;
mRequested = mRecognitionConfig.allowMultipleTriggers;
// TODO: Remove this block if the lower layer supports multiple triggers.
if (mRequested) {
@@ -441,14 +666,16 @@
private void onServiceDiedLocked() {
try {
- if (mActiveListener != null) {
- mActiveListener.onError(SoundTrigger.STATUS_DEAD_OBJECT);
+ if (mKeyphraseListener != null) {
+ mKeyphraseListener.onError(SoundTrigger.STATUS_DEAD_OBJECT);
}
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException in onError", e);
} finally {
- internalClearSoundModelLocked();
- internalClearStateLocked();
+ internalClearKeyphraseSoundModelLocked();
+ internalClearKeyphraseStateLocked();
+ internalClearGenericModelStateLocked();
+ internalClearGlobalStateLocked();
if (mModule != null) {
mModule.detach();
mModule = null;
@@ -457,14 +684,14 @@
}
private int updateRecognitionLocked(boolean notify) {
- if (mModule == null || moduleProperties == null
- || mCurrentSoundModelHandle == INVALID_VALUE || mActiveListener == null) {
+ if (mModule == null || mModuleProperties == null
+ || mCurrentKeyphraseModelHandle == INVALID_VALUE || mKeyphraseListener == null) {
// Nothing to do here.
return STATUS_OK;
}
boolean start = mRequested && !mCallActive && !mServiceDisabled && !mIsPowerSaveMode;
- if (start == mStarted) {
+ if (start == mKeyphraseStarted) {
// No-op.
return STATUS_OK;
}
@@ -472,23 +699,24 @@
// See if the recognition needs to be started.
if (start) {
// Start recognition.
- int status = mModule.startRecognition(mCurrentSoundModelHandle, mRecognitionConfig);
+ int status = mModule.startRecognition(mCurrentKeyphraseModelHandle,
+ mRecognitionConfig);
if (status != SoundTrigger.STATUS_OK) {
Slog.w(TAG, "startRecognition failed with " + status);
// Notify of error if needed.
if (notify) {
try {
- mActiveListener.onError(status);
+ mKeyphraseListener.onError(status);
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException in onError", e);
}
}
} else {
- mStarted = true;
+ mKeyphraseStarted = true;
// Notify of resume if needed.
if (notify) {
try {
- mActiveListener.onRecognitionResumed();
+ mKeyphraseListener.onRecognitionResumed();
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException in onRecognitionResumed", e);
}
@@ -499,7 +727,7 @@
// Stop recognition (only if we haven't been aborted).
int status = STATUS_OK;
if (!mRecognitionAborted) {
- status = mModule.stopRecognition(mCurrentSoundModelHandle);
+ status = mModule.stopRecognition(mCurrentKeyphraseModelHandle);
} else {
mRecognitionAborted = false;
}
@@ -507,17 +735,17 @@
Slog.w(TAG, "stopRecognition call failed with " + status);
if (notify) {
try {
- mActiveListener.onError(status);
+ mKeyphraseListener.onError(status);
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException in onError", e);
}
}
} else {
- mStarted = false;
+ mKeyphraseStarted = false;
// Notify of pause if needed.
if (notify) {
try {
- mActiveListener.onRecognitionPaused();
+ mKeyphraseListener.onRecognitionPaused();
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
}
@@ -527,14 +755,11 @@
}
}
- private void internalClearStateLocked() {
- mStarted = false;
- mRequested = false;
-
- mKeyphraseId = INVALID_VALUE;
- mRecognitionConfig = null;
- mActiveListener = null;
-
+ // internalClearGlobalStateLocked() gets split into two routines. Cleanup that is
+ // specific to keyphrase sound models named as internalClearKeyphraseStateLocked() and
+ // internalClearGlobalStateLocked() for global state. The global cleanup routine will be used
+ // by the cleanup happening with the generic sound models.
+ private void internalClearGlobalStateLocked() {
// Unregister from call state changes.
mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
@@ -545,8 +770,27 @@
}
}
- private void internalClearSoundModelLocked() {
- mCurrentSoundModelHandle = INVALID_VALUE;
+ private void internalClearKeyphraseStateLocked() {
+ mKeyphraseStarted = false;
+ mRequested = false;
+
+ mKeyphraseId = INVALID_VALUE;
+ mRecognitionConfig = null;
+ mKeyphraseListener = null;
+ }
+
+ private void internalClearGenericModelStateLocked() {
+ for (UUID modelId : mGenericModelDataMap.keySet()) {
+ ModelData modelData = mGenericModelDataMap.get(modelId);
+ modelData.clearState();
+ modelData.clearCallback();
+ }
+ }
+
+ // This routine is a replacement for internalClearSoundModelLocked(). However, we
+ // should see why this should be different from internalClearKeyphraseStateLocked().
+ private void internalClearKeyphraseSoundModelLocked() {
+ mCurrentKeyphraseModelHandle = INVALID_VALUE;
mCurrentSoundModel = null;
}
@@ -577,19 +821,251 @@
void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
synchronized (mLock) {
pw.print(" module properties=");
- pw.println(moduleProperties == null ? "null" : moduleProperties);
+ pw.println(mModuleProperties == null ? "null" : mModuleProperties);
pw.print(" keyphrase ID="); pw.println(mKeyphraseId);
- pw.print(" sound model handle="); pw.println(mCurrentSoundModelHandle);
+ pw.print(" sound model handle="); pw.println(mCurrentKeyphraseModelHandle);
pw.print(" sound model UUID=");
pw.println(mCurrentSoundModel == null ? "null" : mCurrentSoundModel.uuid);
pw.print(" current listener=");
- pw.println(mActiveListener == null ? "null" : mActiveListener.asBinder());
+ pw.println(mKeyphraseListener == null ? "null" : mKeyphraseListener.asBinder());
pw.print(" requested="); pw.println(mRequested);
- pw.print(" started="); pw.println(mStarted);
+ pw.print(" started="); pw.println(mKeyphraseStarted);
pw.print(" call active="); pw.println(mCallActive);
pw.print(" power save mode active="); pw.println(mIsPowerSaveMode);
pw.print(" service disabled="); pw.println(mServiceDisabled);
}
}
+
+ private void initializeTelephonyAndPowerStateListeners() {
+ // Get the current call state synchronously for the first recognition.
+ mCallActive = mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE;
+
+ // Register for call state changes when the first call to start recognition occurs.
+ mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
+
+ // Register for power saver mode changes when the first call to start recognition
+ // occurs.
+ if (mPowerSaveModeListener == null) {
+ mPowerSaveModeListener = new PowerSaveModeListener();
+ mContext.registerReceiver(mPowerSaveModeListener,
+ new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
+ }
+ mIsPowerSaveMode = mPowerManager.isPowerSaveMode();
+ }
+
+ private ModelData getOrCreateGenericModelData(UUID modelId) {
+ ModelData modelData = mGenericModelDataMap.get(modelId);
+ if (modelData == null) {
+ modelData = new ModelData(modelId);
+ modelData.setTypeGeneric();
+ mGenericModelDataMap.put(modelId, modelData);
+ }
+ return modelData;
+ }
+
+ // Instead of maintaining a second hashmap of modelHandle -> ModelData, we just
+ // iterate through to find the right object (since we don't expect 100s of models
+ // to be stored).
+ private ModelData getModelDataFor(int modelHandle) {
+ // Fetch ModelData object corresponding to the model handle.
+ for (ModelData model : mGenericModelDataMap.values()) {
+ if (model.getHandle() == modelHandle) {
+ return model;
+ }
+ }
+ return null;
+ }
+
+ // Whether we are allowed to run any recognition at all. The conditions that let us run
+ // a recognition include: no active phone call or not being in a power save mode. Also,
+ // the native service should be enabled.
+ private boolean isRecognitionAllowed() {
+ return !mCallActive && !mServiceDisabled && !mIsPowerSaveMode;
+ }
+
+ private int startGenericRecognitionLocked(ModelData modelData, boolean notify) {
+ IRecognitionStatusCallback callback = modelData.getCallback();
+ int handle = modelData.getHandle();
+ RecognitionConfig config = modelData.getRecognitionConfig();
+ if (callback == null || handle == INVALID_VALUE || config == null) {
+ // Nothing to do here.
+ Slog.w(TAG, "startGenericRecognition: Bad data passed in.");
+ return STATUS_ERROR;
+ }
+
+ if (!isRecognitionAllowed()) {
+ // Nothing to do here.
+ Slog.w(TAG, "startGenericRecognition requested but not allowed.");
+ return STATUS_OK;
+ }
+
+ int status = mModule.startRecognition(handle, config);
+ if (status != SoundTrigger.STATUS_OK) {
+ Slog.w(TAG, "startRecognition failed with " + status);
+ // Notify of error if needed.
+ if (notify) {
+ try {
+ callback.onError(status);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "RemoteException in onError", e);
+ }
+ }
+ } else {
+ modelData.setStarted();
+ // Notify of resume if needed.
+ if (notify) {
+ try {
+ callback.onRecognitionResumed();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "RemoteException in onRecognitionResumed", e);
+ }
+ }
+ }
+ return status;
+ }
+
+ private int stopGenericRecognitionLocked(ModelData modelData, boolean notify) {
+ IRecognitionStatusCallback callback = modelData.getCallback();
+
+ // Stop recognition (only if we haven't been aborted).
+ int status = mModule.stopRecognition(modelData.getHandle());
+ if (status != SoundTrigger.STATUS_OK) {
+ Slog.w(TAG, "stopRecognition call failed with " + status);
+ if (notify) {
+ try {
+ callback.onError(status);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "RemoteException in onError", e);
+ }
+ }
+ } else {
+ modelData.setStopped();
+ // Notify of pause if needed.
+ if (notify) {
+ try {
+ callback.onRecognitionPaused();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
+ }
+ }
+ }
+ return status;
+ }
+
+ // Computes whether we have any recognition running at all (voice or generic). Sets
+ // the mRecognitionRunning variable with the result.
+ private boolean computeRecognitionRunning() {
+ synchronized (mLock) {
+ if (mModuleProperties == null || mModule == null) {
+ mRecognitionRunning = false;
+ return mRecognitionRunning;
+ }
+ if (mKeyphraseListener != null &&
+ mKeyphraseStarted &&
+ mCurrentKeyphraseModelHandle != INVALID_VALUE &&
+ mCurrentSoundModel != null) {
+ mRecognitionRunning = true;
+ return mRecognitionRunning;
+ }
+ for (UUID modelId : mGenericModelDataMap.keySet()) {
+ ModelData modelData = mGenericModelDataMap.get(modelId);
+ if (modelData.modelStarted()) {
+ mRecognitionRunning = true;
+ return mRecognitionRunning;
+ }
+ }
+ mRecognitionRunning = false;
+ }
+ return mRecognitionRunning;
+ }
+
+ // This class encapsulates the callbacks, state, handles and any other information that
+ // represents a model.
+ private static class ModelData {
+ // Model not loaded (and hence not started).
+ static final int MODEL_NOTLOADED = 0;
+
+ // Loaded implies model was successfully loaded. Model not started yet.
+ static final int MODEL_LOADED = 1;
+
+ // Started implies model was successfully loaded and start was called.
+ static final int MODEL_STARTED = 2;
+
+ // One of MODEL_NOTLOADED, MODEL_LOADED, MODEL_STARTED (which implies loaded).
+ private int mModelState;
+
+ private UUID mModelId;
+
+ // One of SoundModel.TYPE_GENERIC or SoundModel.TYPE_KEYPHRASE. Initially set
+ // to SoundModel.TYPE_UNKNOWN;
+ private int mModelType = SoundModel.TYPE_UNKNOWN;
+ private IRecognitionStatusCallback mCallback = null;
+ private SoundModel mSoundModel = null;
+ private RecognitionConfig mRecognitionConfig = null;
+
+
+ // Model handle is an integer used by the HAL as an identifier for sound
+ // models.
+ private int mModelHandle = INVALID_VALUE;
+
+ ModelData(UUID modelId) {
+ mModelId = modelId;
+ }
+
+ synchronized void setTypeGeneric() {
+ mModelType = SoundModel.TYPE_GENERIC_SOUND;
+ }
+
+ synchronized void setCallback(IRecognitionStatusCallback callback) {
+ mCallback = callback;
+ }
+
+ synchronized IRecognitionStatusCallback getCallback() {
+ return mCallback;
+ }
+
+ synchronized boolean isModelLoaded() {
+ return (mModelState == MODEL_LOADED || mModelState == MODEL_STARTED) &&
+ mSoundModel != null;
+ }
+
+ synchronized void setStarted() {
+ mModelState = MODEL_STARTED;
+ }
+
+ synchronized void setStopped() {
+ mModelState = MODEL_LOADED;
+ }
+
+ synchronized boolean modelStarted() {
+ return mModelState == MODEL_STARTED;
+ }
+
+ synchronized void clearState() {
+ mModelState = MODEL_NOTLOADED;
+ mSoundModel = null;
+ mModelHandle = INVALID_VALUE;
+ }
+
+ synchronized void clearCallback() {
+ mCallback = null;
+ }
+
+ synchronized void setHandle(int handle) {
+ mModelHandle = handle;
+ }
+
+ synchronized void setRecognitionConfig(RecognitionConfig config) {
+ mRecognitionConfig = config;
+ }
+
+ synchronized int getHandle() {
+ return mModelHandle;
+ }
+
+ synchronized RecognitionConfig getRecognitionConfig() {
+ return mRecognitionConfig;
+ }
+ }
}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 682f4a4..251f314 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -15,6 +15,7 @@
*/
package com.android.server.soundtrigger;
+import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -47,13 +48,14 @@
* @hide
*/
public class SoundTriggerService extends SystemService {
- static final String TAG = "SoundTriggerService";
- static final boolean DEBUG = false;
+ private static final String TAG = "SoundTriggerService";
+ private static final boolean DEBUG = true;
final Context mContext;
private final SoundTriggerServiceStub mServiceStub;
private final LocalSoundTriggerService mLocalSoundTriggerService;
private SoundTriggerDbHelper mDbHelper;
+ private SoundTriggerHelper mSoundTriggerHelper;
public SoundTriggerService(Context context) {
super(context);
@@ -71,7 +73,8 @@
@Override
public void onBootPhase(int phase) {
if (PHASE_SYSTEM_SERVICES_READY == phase) {
- mLocalSoundTriggerService.initSoundTriggerHelper();
+ initSoundTriggerHelper();
+ mLocalSoundTriggerService.setSoundTriggerHelper(mSoundTriggerHelper);
} else if (PHASE_THIRD_PARTY_APPS_CAN_START == phase) {
mDbHelper = new SoundTriggerDbHelper(mContext);
}
@@ -85,6 +88,20 @@
public void onSwitchUser(int userHandle) {
}
+ private synchronized void initSoundTriggerHelper() {
+ if (mSoundTriggerHelper == null) {
+ mSoundTriggerHelper = new SoundTriggerHelper(mContext);
+ }
+ }
+
+ private synchronized boolean isInitialized() {
+ if (mSoundTriggerHelper == null ) {
+ Slog.e(TAG, "SoundTriggerHelper not initialized.");
+ return false;
+ }
+ return true;
+ }
+
class SoundTriggerServiceStub extends ISoundTriggerService.Stub {
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
@@ -102,19 +119,32 @@
}
@Override
- public void startRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback) {
+ public int startRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback,
+ RecognitionConfig config) {
enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
if (DEBUG) {
Slog.i(TAG, "startRecognition(): Uuid : " + parcelUuid);
}
+ if (!isInitialized()) return STATUS_ERROR;
+
+ GenericSoundModel model = getSoundModel(parcelUuid);
+ if (model == null) {
+ Slog.e(TAG, "Null model in database for id: " + parcelUuid);
+ return STATUS_ERROR;
+ }
+
+ return mSoundTriggerHelper.startGenericRecognition(parcelUuid.getUuid(), model,
+ callback, config);
}
@Override
- public void stopRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback) {
+ public int stopRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback) {
enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
if (DEBUG) {
Slog.i(TAG, "stopRecognition(): Uuid : " + parcelUuid);
}
+ if (!isInitialized()) return STATUS_ERROR;
+ return mSoundTriggerHelper.stopGenericRecognition(parcelUuid.getUuid(), callback);
}
@Override
@@ -123,10 +153,8 @@
if (DEBUG) {
Slog.i(TAG, "getSoundModel(): id = " + soundModelId);
}
- SoundTrigger.GenericSoundModel model = mDbHelper.getGenericSoundModel(soundModelId.getUuid());
- if (model == null) {
- Slog.e(TAG, "Null model in database.");
- }
+ SoundTrigger.GenericSoundModel model = mDbHelper.getGenericSoundModel(
+ soundModelId.getUuid());
return model;
}
@@ -157,38 +185,49 @@
mContext = context;
}
- void initSoundTriggerHelper() {
- if (mSoundTriggerHelper == null) {
- mSoundTriggerHelper = new SoundTriggerHelper(mContext);
- }
+ synchronized void setSoundTriggerHelper(SoundTriggerHelper helper) {
+ mSoundTriggerHelper = helper;
}
@Override
public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) {
- return mSoundTriggerHelper.startRecognition(keyphraseId, soundModel, listener,
+ if (!isInitialized()) return STATUS_ERROR;
+ return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, listener,
recognitionConfig);
}
@Override
- public int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
- return mSoundTriggerHelper.stopRecognition(keyphraseId, listener);
+ public synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
+ if (!isInitialized()) return STATUS_ERROR;
+ return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener);
}
@Override
public void stopAllRecognitions() {
+ if (!isInitialized()) return;
mSoundTriggerHelper.stopAllRecognitions();
}
@Override
public ModuleProperties getModuleProperties() {
+ if (!isInitialized()) return null;
return mSoundTriggerHelper.getModuleProperties();
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (!isInitialized()) return;
mSoundTriggerHelper.dump(fd, pw, args);
}
+
+ private synchronized boolean isInitialized() {
+ if (mSoundTriggerHelper == null ) {
+ Slog.e(TAG, "SoundTriggerHelper not initialized.");
+ return false;
+ }
+ return true;
+ }
}
private void enforceCallingPermission(String permission) {
diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index bec1e4f..84cffe1 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -191,6 +191,11 @@
}
@Override
+ public File getDataDir() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public File getFilesDir() {
throw new UnsupportedOperationException();
}
diff --git a/tests/SoundTriggerTestApp/Android.mk b/tests/SoundTriggerTestApp/Android.mk
index 7bcab5e..c327b09 100644
--- a/tests/SoundTriggerTestApp/Android.mk
+++ b/tests/SoundTriggerTestApp/Android.mk
@@ -8,5 +8,6 @@
LOCAL_MODULE_TAGS := optional
LOCAL_PRIVILEGED_MODULE := true
+LOCAL_CERTIFICATE := platform
include $(BUILD_PACKAGE)
diff --git a/tests/SoundTriggerTestApp/AndroidManifest.xml b/tests/SoundTriggerTestApp/AndroidManifest.xml
index 40619da..a72b3dd 100644
--- a/tests/SoundTriggerTestApp/AndroidManifest.xml
+++ b/tests/SoundTriggerTestApp/AndroidManifest.xml
@@ -2,16 +2,22 @@
package="com.android.test.soundtrigger">
<uses-permission android:name="android.permission.MANAGE_SOUND_TRIGGER" />
- <application
- android:permission="android.permission.MANAGE_SOUND_TRIGGER">
+ <application>
<activity
android:name="TestSoundTriggerActivity"
android:label="SoundTrigger Test Application"
- android:theme="@android:style/Theme.Material.Light.Voice">
+ android:theme="@android:style/Theme.Material">
+ <!--
<intent-filter>
<action android:name="com.android.intent.action.MANAGE_SOUND_TRIGGER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
+ -->
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
</activity>
</application>
</manifest>
diff --git a/tests/SoundTriggerTestApp/res/layout/main.xml b/tests/SoundTriggerTestApp/res/layout/main.xml
index 9d2b9d9..5ecc770 100644
--- a/tests/SoundTriggerTestApp/res/layout/main.xml
+++ b/tests/SoundTriggerTestApp/res/layout/main.xml
@@ -18,6 +18,11 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:orientation="vertical"
+ >
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
>
<Button
@@ -37,7 +42,57 @@
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:text="@string/start_recog"
+ android:onClick="onStartRecognitionButtonClicked"
+ android:padding="20dp" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/stop_recog"
+ android:onClick="onStopRecognitionButtonClicked"
+ android:padding="20dp" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:text="@string/unenroll"
android:onClick="onUnEnrollButtonClicked"
android:padding="20dp" />
-</LinearLayout>
\ No newline at end of file
+
+</LinearLayout>
+
+<RadioGroup xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:padding="20dp"
+ android:orientation="vertical">
+ <RadioButton android:id="@+id/model_one"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/model_one"
+ android:onClick="onRadioButtonClicked"/>
+ <RadioButton android:id="@+id/model_two"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/model_two"
+ android:onClick="onRadioButtonClicked"/>
+ <RadioButton android:id="@+id/model_three"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/model_three"
+ android:onClick="onRadioButtonClicked"/>
+</RadioGroup>
+
+ <TextView
+ android:id="@+id/console"
+ android:gravity="left"
+ android:paddingTop="20pt"
+ android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:maxLines="40"
+ android:textSize="14dp"
+ android:scrollbars = "vertical"
+ android:text="@string/none">
+ </TextView>
+</LinearLayout>
diff --git a/tests/SoundTriggerTestApp/res/values/strings.xml b/tests/SoundTriggerTestApp/res/values/strings.xml
index 07bac2a..5f0fb1d 100644
--- a/tests/SoundTriggerTestApp/res/values/strings.xml
+++ b/tests/SoundTriggerTestApp/res/values/strings.xml
@@ -16,7 +16,13 @@
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="enroll">Enroll</string>
- <string name="reenroll">Re-enroll</string>
- <string name="unenroll">Un-enroll</string>
-</resources>
\ No newline at end of file
+ <string name="enroll">Load</string>
+ <string name="reenroll">Re-load</string>
+ <string name="unenroll">Un-load</string>
+ <string name="start_recog">Start</string>
+ <string name="stop_recog">Stop</string>
+ <string name="model_one">Model One</string>
+ <string name="model_two">Model Two</string>
+ <string name="model_three">Model Three</string>
+ <string name="none">Debug messages appear here:</string>
+</resources>
diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java
index 4702835..1c95c25 100644
--- a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java
+++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.hardware.soundtrigger.SoundTrigger;
import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
+import android.media.soundtrigger.SoundTriggerDetector;
import android.media.soundtrigger.SoundTriggerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -28,6 +29,7 @@
import com.android.internal.app.ISoundTriggerService;
+import java.lang.RuntimeException;
import java.util.UUID;
/**
@@ -56,6 +58,9 @@
*/
public boolean addOrUpdateSoundModel(GenericSoundModel soundModel) {
try {
+ if (soundModel == null) {
+ throw new RuntimeException("Bad sound model");
+ }
mSoundTriggerService.updateSoundModel(soundModel);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in updateSoundModel", e);
@@ -112,4 +117,10 @@
public void deleteSoundModelUsingManager(UUID modelId) {
mSoundTriggerManager.deleteModel(modelId);
}
+
+ public SoundTriggerDetector createSoundTriggerDetector(UUID modelId,
+ SoundTriggerDetector.Callback callback) {
+ return mSoundTriggerManager.createSoundTriggerDetector(modelId, callback, null);
+ }
+
}
diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java
index 966179b..96a6966 100644
--- a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java
+++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java
@@ -22,11 +22,17 @@
import android.app.Activity;
import android.hardware.soundtrigger.SoundTrigger;
import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
+import android.media.AudioFormat;
+import android.media.soundtrigger.SoundTriggerDetector;
import android.media.soundtrigger.SoundTriggerManager;
+import android.text.Editable;
+import android.text.method.ScrollingMovementMethod;
import android.os.Bundle;
import android.os.UserManager;
import android.util.Log;
import android.view.View;
+import android.widget.RadioButton;
+import android.widget.TextView;
import android.widget.Toast;
public class TestSoundTriggerActivity extends Activity {
@@ -35,42 +41,75 @@
private SoundTriggerUtil mSoundTriggerUtil;
private Random mRandom;
- private UUID mModelUuid = UUID.randomUUID();
+ private UUID mModelUuid1 = UUID.randomUUID();
private UUID mModelUuid2 = UUID.randomUUID();
+ private UUID mModelUuid3 = UUID.randomUUID();
private UUID mVendorUuid = UUID.randomUUID();
+ private SoundTriggerDetector mDetector1 = null;
+ private SoundTriggerDetector mDetector2 = null;
+ private SoundTriggerDetector mDetector3 = null;
+
+ private TextView mDebugView = null;
+ private int mSelectedModelId = 1;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
if (DBG) Log.d(TAG, "onCreate");
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
+ mDebugView = (TextView) findViewById(R.id.console);
+ mDebugView.setText(mDebugView.getText(), TextView.BufferType.EDITABLE);
+ mDebugView.setMovementMethod(new ScrollingMovementMethod());
mSoundTriggerUtil = new SoundTriggerUtil(this);
mRandom = new Random();
}
+ private void postMessage(String msg) {
+ Log.i(TAG, "Posted: " + msg);
+ ((Editable) mDebugView.getText()).append(msg + "\n");
+ }
+
+ private UUID getSelectedUuid() {
+ if (mSelectedModelId == 2) return mModelUuid2;
+ if (mSelectedModelId == 3) return mModelUuid3;
+ return mModelUuid1; // Default.
+ }
+
+ private void setDetector(SoundTriggerDetector detector) {
+ if (mSelectedModelId == 2) mDetector2 = detector;
+ if (mSelectedModelId == 3) mDetector3 = detector;
+ mDetector1 = detector;
+ }
+
+ private SoundTriggerDetector getDetector() {
+ if (mSelectedModelId == 2) return mDetector2;
+ if (mSelectedModelId == 3) return mDetector3;
+ return mDetector1;
+ }
+
/**
* Called when the user clicks the enroll button.
* Performs a fresh enrollment.
*/
public void onEnrollButtonClicked(View v) {
+ postMessage("Loading model: " + mSelectedModelId);
// Generate a fake model to push.
byte[] data = new byte[1024];
mRandom.nextBytes(data);
- GenericSoundModel model = new GenericSoundModel(mModelUuid, mVendorUuid, data);
+ UUID modelUuid = getSelectedUuid();
+ GenericSoundModel model = new GenericSoundModel(modelUuid, mVendorUuid, data);
boolean status = mSoundTriggerUtil.addOrUpdateSoundModel(model);
if (status) {
Toast.makeText(
- this, "Successfully created sound trigger model UUID=" + mModelUuid, Toast.LENGTH_SHORT)
- .show();
+ this, "Successfully created sound trigger model UUID=" + modelUuid,
+ Toast.LENGTH_SHORT).show();
} else {
- Toast.makeText(this, "Failed to enroll!!!" + mModelUuid, Toast.LENGTH_SHORT).show();
+ Toast.makeText(this, "Failed to enroll!!!" + modelUuid, Toast.LENGTH_SHORT).show();
}
// Test the SoundManager API.
- SoundTriggerManager.Model tmpModel = SoundTriggerManager.Model.create(mModelUuid2,
- mVendorUuid, data);
- mSoundTriggerUtil.addOrUpdateSoundModel(tmpModel);
}
/**
@@ -78,12 +117,14 @@
* Clears the enrollment information for the user.
*/
public void onUnEnrollButtonClicked(View v) {
- GenericSoundModel soundModel = mSoundTriggerUtil.getSoundModel(mModelUuid);
+ postMessage("Unloading model: " + mSelectedModelId);
+ UUID modelUuid = getSelectedUuid();
+ GenericSoundModel soundModel = mSoundTriggerUtil.getSoundModel(modelUuid);
if (soundModel == null) {
Toast.makeText(this, "Sound model not found!!!", Toast.LENGTH_SHORT).show();
return;
}
- boolean status = mSoundTriggerUtil.deleteSoundModel(mModelUuid);
+ boolean status = mSoundTriggerUtil.deleteSoundModel(mModelUuid1);
if (status) {
Toast.makeText(this, "Successfully deleted model UUID=" + soundModel.uuid,
Toast.LENGTH_SHORT)
@@ -91,7 +132,6 @@
} else {
Toast.makeText(this, "Failed to delete sound model!!!", Toast.LENGTH_SHORT).show();
}
- mSoundTriggerUtil.deleteSoundModelUsingManager(mModelUuid2);
}
/**
@@ -99,7 +139,9 @@
* Uses the previously enrolled sound model and makes changes to it before pushing it back.
*/
public void onReEnrollButtonClicked(View v) {
- GenericSoundModel soundModel = mSoundTriggerUtil.getSoundModel(mModelUuid);
+ postMessage("Re-loading model: " + mSelectedModelId);
+ UUID modelUuid = getSelectedUuid();
+ GenericSoundModel soundModel = mSoundTriggerUtil.getSoundModel(modelUuid);
if (soundModel == null) {
Toast.makeText(this, "Sound model not found!!!", Toast.LENGTH_SHORT).show();
return;
@@ -118,4 +160,86 @@
Toast.makeText(this, "Failed to re-enroll!!!", Toast.LENGTH_SHORT).show();
}
}
+
+ public void onStartRecognitionButtonClicked(View v) {
+ UUID modelUuid = getSelectedUuid();
+ SoundTriggerDetector detector = getDetector();
+ if (detector == null) {
+ Log.i(TAG, "Created an instance of the SoundTriggerDetector.");
+ detector = mSoundTriggerUtil.createSoundTriggerDetector(modelUuid,
+ new DetectorCallback());
+ setDetector(detector);
+ }
+ postMessage("Triggering start recognition for model: " + mSelectedModelId);
+ if (!detector.startRecognition(
+ SoundTriggerDetector.RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS)) {
+ Log.e(TAG, "Fast failure attempting to start recognition.");
+ }
+ }
+
+ public void onStopRecognitionButtonClicked(View v) {
+ SoundTriggerDetector detector = getDetector();
+ if (detector == null) {
+ Log.e(TAG, "Stop called on null detector.");
+ return;
+ }
+ postMessage("Triggering stop recognition for model: " + mSelectedModelId);
+ if (!detector.stopRecognition()) {
+ Log.e(TAG, "Fast failure attempting to stop recognition.");
+ }
+ }
+
+ public void onRadioButtonClicked(View view) {
+ // Is the button now checked?
+ boolean checked = ((RadioButton) view).isChecked();
+ // Check which radio button was clicked
+ switch(view.getId()) {
+ case R.id.model_one:
+ if (checked) mSelectedModelId = 1;
+ postMessage("Selected model one.");
+ break;
+ case R.id.model_two:
+ if (checked) mSelectedModelId = 2;
+ postMessage("Selected model two.");
+ break;
+ case R.id.model_three:
+ if (checked) mSelectedModelId = 3;
+ postMessage("Selected model three.");
+ break;
+ }
+ }
+
+ // Implementation of SoundTriggerDetector.Callback.
+ public class DetectorCallback extends SoundTriggerDetector.Callback {
+ public void onAvailabilityChanged(int status) {
+ postMessage("Availability changed to: " + status);
+ }
+
+ public void onDetected(SoundTriggerDetector.EventPayload event) {
+ postMessage("onDetected(): " + eventPayloadToString(event));
+ }
+
+ public void onError() {
+ postMessage("onError()");
+ }
+
+ public void onRecognitionPaused() {
+ postMessage("onRecognitionPaused()");
+ }
+
+ public void onRecognitionResumed() {
+ postMessage("onRecognitionResumed()");
+ }
+ }
+
+ private String eventPayloadToString(SoundTriggerDetector.EventPayload event) {
+ String result = "EventPayload(";
+ AudioFormat format = event.getCaptureAudioFormat();
+ result = result + "AudioFormat: " + ((format == null) ? "null" : format.toString());
+ byte[] triggerAudio = event.getTriggerAudio();
+ result = result + "TriggerAudio: " + (triggerAudio == null ? "null" : triggerAudio.length);
+ result = result + "CaptureSession: " + event.getCaptureSession();
+ result += " )";
+ return result;
+ }
}
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index cb82ac3..f9d35ab 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -47,6 +47,7 @@
proto/ProtoHelpers.cpp \
proto/TableProtoDeserializer.cpp \
proto/TableProtoSerializer.cpp \
+ split/TableSplitter.cpp \
unflatten/BinaryResourceParser.cpp \
unflatten/ResChunkPullParser.cpp \
util/BigBuffer.cpp \
@@ -90,6 +91,7 @@
link/XmlReferenceLinker_test.cpp \
process/SymbolTable_test.cpp \
proto/TableProtoSerializer_test.cpp \
+ split/TableSplitter_test.cpp \
util/BigBuffer_test.cpp \
util/Maybe_test.cpp \
util/StringPiece_test.cpp \
@@ -103,6 +105,7 @@
ResourceParser_test.cpp \
ResourceTable_test.cpp \
ResourceUtils_test.cpp \
+ SdkConstants_test.cpp \
StringPool_test.cpp \
ValueVisitor_test.cpp \
xml/XmlDom_test.cpp \
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 4bea129..19bd521 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -30,7 +30,8 @@
namespace aapt {
-struct PrintVisitor : public ValueVisitor {
+class PrintVisitor : public ValueVisitor {
+public:
using ValueVisitor::visit;
void visit(Attribute* attr) override {
@@ -69,7 +70,11 @@
for (const auto& entry : style->entries) {
std::cout << "\n ";
if (entry.key.name) {
- std::cout << entry.key.name.value().package << ":" << entry.key.name.value().entry;
+ const ResourceName& name = entry.key.name.value();
+ if (!name.package.empty()) {
+ std::cout << name.package << ":";
+ }
+ std::cout << name.entry;
}
if (entry.key.id) {
@@ -89,7 +94,21 @@
}
void visit(Styleable* styleable) override {
- styleable->print(&std::cout);
+ std::cout << "(styleable)";
+ for (const auto& attr : styleable->entries) {
+ std::cout << "\n ";
+ if (attr.name) {
+ const ResourceName& name = attr.name.value();
+ if (!name.package.empty()) {
+ std::cout << name.package << ":";
+ }
+ std::cout << name.entry;
+ }
+
+ if (attr.id) {
+ std::cout << "(" << attr.id.value() << ")";
+ }
+ }
}
void visitItem(Item* item) override {
@@ -97,7 +116,9 @@
}
};
-void Debug::printTable(ResourceTable* table) {
+void Debug::printTable(ResourceTable* table, const DebugPrintTableOptions& options) {
+ PrintVisitor visitor;
+
for (auto& package : table->packages) {
std::cout << "Package name=" << package->name;
if (package->id) {
@@ -106,7 +127,7 @@
std::cout << std::endl;
for (const auto& type : package->types) {
- std::cout << " type " << type->type;
+ std::cout << "\n type " << type->type;
if (type->id) {
std::cout << " id=" << std::hex << (int) type->id.value() << std::dec;
}
@@ -142,10 +163,12 @@
std::cout << std::endl;
- PrintVisitor visitor;
for (const auto& value : entry->values) {
std::cout << " (" << value->config << ") ";
value->value->accept(&visitor);
+ if (options.showSources && !value->value->getSource().path.empty()) {
+ std::cout << " src=" << value->value->getSource();
+ }
std::cout << std::endl;
}
}
diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h
index ba05be9..fbe6477 100644
--- a/tools/aapt2/Debug.h
+++ b/tools/aapt2/Debug.h
@@ -25,8 +25,12 @@
namespace aapt {
+struct DebugPrintTableOptions {
+ bool showSources = false;
+};
+
struct Debug {
- static void printTable(ResourceTable* table);
+ static void printTable(ResourceTable* table, const DebugPrintTableOptions& options = {});
static void printStyleGraph(ResourceTable* table,
const ResourceName& targetStyle);
static void dumpHex(const void* data, size_t len);
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 3e73be4..8d734f3 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -277,20 +277,31 @@
const Source& source,
const StringPiece16& path,
IDiagnostics* diag) {
- return addFileReference(name, config, source, path, resolveValueCollision, diag);
+ return addFileReferenceImpl(name, config, source, path, nullptr, kValidNameChars, diag);
}
-bool ResourceTable::addFileReference(const ResourceNameRef& name,
- const ConfigDescription& config,
- const Source& source,
- const StringPiece16& path,
- std::function<int(Value*,Value*)> conflictResolver,
- IDiagnostics* diag) {
+bool ResourceTable::addFileReferenceAllowMangled(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const Source& source,
+ const StringPiece16& path,
+ io::IFile* file,
+ IDiagnostics* diag) {
+ return addFileReferenceImpl(name, config, source, path, file, kValidNameMangledChars, diag);
+}
+
+bool ResourceTable::addFileReferenceImpl(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const Source& source,
+ const StringPiece16& path,
+ io::IFile* file,
+ const char16_t* validChars,
+ IDiagnostics* diag) {
std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>(
stringPool.makeRef(path));
fileRef->setSource(source);
+ fileRef->file = file;
return addResourceImpl(name, ResourceId{}, config, StringPiece{}, std::move(fileRef),
- kValidNameChars, conflictResolver, diag);
+ kValidNameChars, resolveValueCollision, diag);
}
bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 8ffff1f..7f5c2b8 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -23,6 +23,7 @@
#include "ResourceValues.h"
#include "Source.h"
#include "StringPool.h"
+#include "io/File.h"
#include <android-base/macros.h>
#include <map>
@@ -202,17 +203,17 @@
IDiagnostics* diag);
bool addFileReference(const ResourceNameRef& name,
- const ConfigDescription& config,
- const Source& source,
- const StringPiece16& path,
- IDiagnostics* diag);
+ const ConfigDescription& config,
+ const Source& source,
+ const StringPiece16& path,
+ IDiagnostics* diag);
- bool addFileReference(const ResourceNameRef& name,
- const ConfigDescription& config,
- const Source& source,
- const StringPiece16& path,
- std::function<int(Value*,Value*)> conflictResolver,
- IDiagnostics* diag);
+ bool addFileReferenceAllowMangled(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const Source& source,
+ const StringPiece16& path,
+ io::IFile* file,
+ IDiagnostics* diag);
/**
* Same as addResource, but doesn't verify the validity of the name. This is used
@@ -280,6 +281,14 @@
private:
ResourceTablePackage* findOrCreatePackage(const StringPiece16& name);
+ bool addFileReferenceImpl(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const Source& source,
+ const StringPiece16& path,
+ io::IFile* file,
+ const char16_t* validChars,
+ IDiagnostics* diag);
+
bool addResourceImpl(const ResourceNameRef& name,
ResourceId resId,
const ConfigDescription& config,
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index ab9c792..dd7ff01 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -18,6 +18,7 @@
#include "ResourceUtils.h"
#include "ResourceValues.h"
#include "ValueVisitor.h"
+#include "io/File.h"
#include "util/Util.h"
#include <androidfw/ResourceTypes.h>
@@ -190,6 +191,7 @@
FileReference* FileReference::clone(StringPool* newPool) const {
FileReference* fr = new FileReference(newPool->makeRef(*path));
+ fr->file = file;
fr->mComment = mComment;
fr->mSource = mSource;
return fr;
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index dc2e28e..43354ac 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -20,6 +20,7 @@
#include "Diagnostics.h"
#include "Resource.h"
#include "StringPool.h"
+#include "io/File.h"
#include "util/Maybe.h"
#include <array>
@@ -226,6 +227,11 @@
struct FileReference : public BaseItem<FileReference> {
StringPool::Ref path;
+ /**
+ * A handle to the file object from which this file can be read.
+ */
+ io::IFile* file = nullptr;
+
FileReference() = default;
FileReference(const StringPool::Ref& path);
diff --git a/tools/aapt2/SdkConstants_test.cpp b/tools/aapt2/SdkConstants_test.cpp
new file mode 100644
index 0000000..e81f412
--- /dev/null
+++ b/tools/aapt2/SdkConstants_test.cpp
@@ -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.
+ */
+
+#include "SdkConstants.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(SdkConstantsTest, FirstAttributeIsSdk1) {
+ EXPECT_EQ(1u, findAttributeSdkLevel(ResourceId(0x01010000)));
+}
+
+TEST(SdkConstantsTest, AllAttributesAfterLollipopAreLollipopMR1) {
+ EXPECT_EQ(SDK_LOLLIPOP, findAttributeSdkLevel(ResourceId(0x010103f7)));
+ EXPECT_EQ(SDK_LOLLIPOP, findAttributeSdkLevel(ResourceId(0x010104ce)));
+
+ EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104cf)));
+ EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104d8)));
+
+ EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104d9)));
+ EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x0101ffff)));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp
index 0dd8e18..5f9719e 100644
--- a/tools/aapt2/compile/Compile.cpp
+++ b/tools/aapt2/compile/Compile.cpp
@@ -424,8 +424,17 @@
class CompileContext : public IAaptContext {
private:
StdErrDiagnostics mDiagnostics;
+ bool mVerbose = false;
public:
+ void setVerbose(bool val) {
+ mVerbose = val;
+ }
+
+ bool verbose() override {
+ return mVerbose;
+ }
+
IDiagnostics* getDiagnostics() override {
return &mDiagnostics;
}
@@ -453,8 +462,10 @@
* Entry point for compilation phase. Parses arguments and dispatches to the correct steps.
*/
int compile(const std::vector<StringPiece>& args) {
+ CompileContext context;
CompileOptions options;
+ bool verbose = false;
Flags flags = Flags()
.requiredFlag("-o", "Output path", &options.outputPath)
.optionalFlag("--dir", "Directory to scan for resources", &options.resDir)
@@ -462,12 +473,13 @@
"(en-XA and ar-XB)", &options.pseudolocalize)
.optionalSwitch("--legacy", "Treat errors that used to be valid in AAPT as warnings",
&options.legacyMode)
- .optionalSwitch("-v", "Enables verbose logging", &options.verbose);
+ .optionalSwitch("-v", "Enables verbose logging", &verbose);
if (!flags.parse("aapt2 compile", args, &std::cerr)) {
return 1;
}
- CompileContext context;
+ context.setVerbose(verbose);
+
std::unique_ptr<IArchiveWriter> archiveWriter;
std::vector<ResourcePathData> inputData;
diff --git a/tools/aapt2/dump/Dump.cpp b/tools/aapt2/dump/Dump.cpp
index 915fae8..ad7de0a 100644
--- a/tools/aapt2/dump/Dump.cpp
+++ b/tools/aapt2/dump/Dump.cpp
@@ -103,21 +103,32 @@
return nullptr;
}
+ bool verbose() override {
+ return mVerbose;
+ }
+
+ void setVerbose(bool val) {
+ mVerbose = val;
+ }
+
private:
StdErrDiagnostics mDiagnostics;
+ bool mVerbose = false;
};
/**
* Entry point for dump command.
*/
int dump(const std::vector<StringPiece>& args) {
- //DumpOptions options;
- Flags flags = Flags();
+ bool verbose = false;
+ Flags flags = Flags()
+ .optionalSwitch("-v", "increase verbosity of output", &verbose);
if (!flags.parse("aapt2 dump", args, &std::cerr)) {
return 1;
}
DumpContext context;
+ context.setVerbose(verbose);
for (const std::string& arg : flags.getArgs()) {
tryDumpFile(&context, arg);
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 3437ac0..d83f6def 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -38,6 +38,7 @@
#include "process/IResourceTableConsumer.h"
#include "process/SymbolTable.h"
#include "proto/ProtoSerialize.h"
+#include "split/TableSplitter.h"
#include "unflatten/BinaryResourceParser.h"
#include "util/Files.h"
#include "util/StringPiece.h"
@@ -63,15 +64,14 @@
bool noAutoVersion = false;
bool staticLib = false;
bool generateNonFinalIds = false;
- bool verbose = false;
bool outputToDirectory = false;
bool autoAddOverlay = false;
bool doNotCompressAnything = false;
std::vector<std::string> extensionsToNotCompress;
Maybe<std::u16string> privateSymbols;
ManifestFixerOptions manifestFixerOptions;
- IConfigFilter* configFilter = nullptr;
std::unordered_set<std::string> products;
+ TableSplitterOptions tableSplitterOptions;
};
struct LinkContext : public IAaptContext {
@@ -80,6 +80,7 @@
std::u16string mCompilationPackage;
uint8_t mPackageId;
std::unique_ptr<ISymbolTable> mSymbols;
+ bool mVerbose = false;
IDiagnostics* getDiagnostics() override {
return &mDiagnostics;
@@ -100,8 +101,376 @@
ISymbolTable* getExternalSymbols() override {
return mSymbols.get();
}
+
+ bool verbose() override {
+ return mVerbose;
+ }
};
+static bool copyFileToArchive(io::IFile* file, const std::string& outPath,
+ uint32_t compressionFlags,
+ IArchiveWriter* writer, IAaptContext* context) {
+ std::unique_ptr<io::IData> data = file->openAsData();
+ if (!data) {
+ context->getDiagnostics()->error(DiagMessage(file->getSource())
+ << "failed to open file");
+ return false;
+ }
+
+ CompiledFileInputStream inputStream(data->data(), data->size());
+ if (!inputStream.CompiledFile()) {
+ context->getDiagnostics()->error(DiagMessage(file->getSource())
+ << "invalid compiled file header");
+ return false;
+ }
+
+ if (context->verbose()) {
+ context->getDiagnostics()->note(DiagMessage() << "writing " << outPath << " to archive");
+ }
+
+ if (writer->startEntry(outPath, compressionFlags)) {
+ if (writer->writeEntry(reinterpret_cast<const uint8_t*>(inputStream.data()),
+ inputStream.size())) {
+ if (writer->finishEntry()) {
+ return true;
+ }
+ }
+ }
+
+ context->getDiagnostics()->error(DiagMessage() << "failed to write file " << outPath);
+ return false;
+}
+
+static bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
+ bool keepRawValues, IArchiveWriter* writer, IAaptContext* context) {
+ BigBuffer buffer(1024);
+ XmlFlattenerOptions options = {};
+ options.keepRawValues = keepRawValues;
+ options.maxSdkLevel = maxSdkLevel;
+ XmlFlattener flattener(&buffer, options);
+ if (!flattener.consume(context, xmlRes)) {
+ return false;
+ }
+
+ if (context->verbose()) {
+ DiagMessage msg;
+ msg << "writing " << path << " to archive";
+ if (maxSdkLevel) {
+ msg << " maxSdkLevel=" << maxSdkLevel.value();
+ }
+ context->getDiagnostics()->note(msg);
+ }
+
+ if (writer->startEntry(path, ArchiveEntry::kCompress)) {
+ if (writer->writeEntry(buffer)) {
+ if (writer->finishEntry()) {
+ return true;
+ }
+ }
+ }
+ context->getDiagnostics()->error(DiagMessage() << "failed to write " << path << " to archive");
+ return false;
+}
+
+/*static std::unique_ptr<ResourceTable> loadTable(const Source& source, const void* data, size_t len,
+ IDiagnostics* diag) {
+ std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
+ BinaryResourceParser parser(diag, table.get(), source, data, len);
+ if (!parser.parse()) {
+ return {};
+ }
+ return table;
+}*/
+
+static std::unique_ptr<ResourceTable> loadTableFromPb(const Source& source,
+ const void* data, size_t len,
+ IDiagnostics* diag) {
+ pb::ResourceTable pbTable;
+ if (!pbTable.ParseFromArray(data, len)) {
+ diag->error(DiagMessage(source) << "invalid compiled table");
+ return {};
+ }
+
+ std::unique_ptr<ResourceTable> table = deserializeTableFromPb(pbTable, source, diag);
+ if (!table) {
+ return {};
+ }
+ return table;
+}
+
+/**
+ * Inflates an XML file from the source path.
+ */
+static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) {
+ std::ifstream fin(path, std::ifstream::binary);
+ if (!fin) {
+ diag->error(DiagMessage(path) << strerror(errno));
+ return {};
+ }
+ return xml::inflate(&fin, diag, Source(path));
+}
+
+static std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(const Source& source,
+ const void* data, size_t len,
+ IDiagnostics* diag) {
+ CompiledFileInputStream inputStream(data, len);
+ if (!inputStream.CompiledFile()) {
+ diag->error(DiagMessage(source) << "invalid compiled file header");
+ return {};
+ }
+
+ const uint8_t* xmlData = reinterpret_cast<const uint8_t*>(inputStream.data());
+ const size_t xmlDataLen = inputStream.size();
+
+ std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(xmlData, xmlDataLen, diag, source);
+ if (!xmlRes) {
+ return {};
+ }
+ return xmlRes;
+}
+
+static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source,
+ const void* data, size_t len,
+ IDiagnostics* diag) {
+ CompiledFileInputStream inputStream(data, len);
+ const pb::CompiledFile* pbFile = inputStream.CompiledFile();
+ if (!pbFile) {
+ diag->error(DiagMessage(source) << "invalid compiled file header");
+ return {};
+ }
+
+ std::unique_ptr<ResourceFile> resFile = deserializeCompiledFileFromPb(*pbFile, source, diag);
+ if (!resFile) {
+ return {};
+ }
+ return resFile;
+}
+
+struct ResourceFileFlattenerOptions {
+ bool noAutoVersion = false;
+ bool keepRawValues = false;
+ bool doNotCompressAnything = false;
+ std::vector<std::string> extensionsToNotCompress;
+};
+
+class ResourceFileFlattener {
+public:
+ ResourceFileFlattener(const ResourceFileFlattenerOptions& options,
+ IAaptContext* context, proguard::KeepSet* keepSet) :
+ mOptions(options), mContext(context), mKeepSet(keepSet) {
+ }
+
+ bool flatten(ResourceTable* table, IArchiveWriter* archiveWriter);
+
+private:
+ struct FileOperation {
+ io::IFile* fileToCopy;
+ std::unique_ptr<xml::XmlResource> xmlToFlatten;
+ std::string dstPath;
+ };
+
+ uint32_t getCompressionFlags(const StringPiece& str);
+
+ std::unique_ptr<xml::XmlResource> linkAndVersionXmlFile(const ResourceEntry* entry,
+ const ResourceFile& fileDesc,
+ io::IFile* file,
+ ResourceTable* table);
+
+ ResourceFileFlattenerOptions mOptions;
+ IAaptContext* mContext;
+ proguard::KeepSet* mKeepSet;
+};
+
+uint32_t ResourceFileFlattener::getCompressionFlags(const StringPiece& str) {
+ if (mOptions.doNotCompressAnything) {
+ return 0;
+ }
+
+ for (const std::string& extension : mOptions.extensionsToNotCompress) {
+ if (util::stringEndsWith<char>(str, extension)) {
+ return 0;
+ }
+ }
+ return ArchiveEntry::kCompress;
+}
+
+std::unique_ptr<xml::XmlResource> ResourceFileFlattener::linkAndVersionXmlFile(
+ const ResourceEntry* entry,
+ const ResourceFile& fileDesc,
+ io::IFile* file,
+ ResourceTable* table) {
+ const StringPiece srcPath = file->getSource().path;
+ if (mContext->verbose()) {
+ mContext->getDiagnostics()->note(DiagMessage() << "linking " << srcPath);
+ }
+
+ std::unique_ptr<io::IData> data = file->openAsData();
+ if (!data) {
+ mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file");
+ return {};
+ }
+
+ std::unique_ptr<xml::XmlResource> xmlRes;
+ if (util::stringEndsWith<char>(srcPath, ".flat")) {
+ xmlRes = loadBinaryXmlSkipFileExport(file->getSource(), data->data(), data->size(),
+ mContext->getDiagnostics());
+ } else {
+ xmlRes = xml::inflate(data->data(), data->size(), mContext->getDiagnostics(),
+ file->getSource());
+ }
+
+ if (!xmlRes) {
+ return {};
+ }
+
+ // Copy the the file description header.
+ xmlRes->file = fileDesc;
+
+ XmlReferenceLinker xmlLinker;
+ if (!xmlLinker.consume(mContext, xmlRes.get())) {
+ return {};
+ }
+
+ if (!proguard::collectProguardRules(xmlRes->file.source, xmlRes.get(), mKeepSet)) {
+ return {};
+ }
+
+ if (!mOptions.noAutoVersion) {
+ // Find the first SDK level used that is higher than this defined config and
+ // not superseded by a lower or equal SDK level resource.
+ for (int sdkLevel : xmlLinker.getSdkLevels()) {
+ if (sdkLevel > xmlRes->file.config.sdkVersion) {
+ if (!shouldGenerateVersionedResource(entry, xmlRes->file.config, sdkLevel)) {
+ // If we shouldn't generate a versioned resource, stop checking.
+ break;
+ }
+
+ ResourceFile versionedFileDesc = xmlRes->file;
+ versionedFileDesc.config.sdkVersion = sdkLevel;
+
+ if (mContext->verbose()) {
+ mContext->getDiagnostics()->note(DiagMessage(versionedFileDesc.source)
+ << "auto-versioning resource from config '"
+ << xmlRes->file.config << "' -> '"
+ << versionedFileDesc.config << "'");
+ }
+
+ std::u16string genPath = util::utf8ToUtf16(ResourceUtils::buildResourceFileName(
+ versionedFileDesc, mContext->getNameMangler()));
+
+ bool added = table->addFileReferenceAllowMangled(versionedFileDesc.name,
+ versionedFileDesc.config,
+ versionedFileDesc.source,
+ genPath,
+ file,
+ mContext->getDiagnostics());
+ if (!added) {
+ return {};
+ }
+ break;
+ }
+ }
+ }
+ return xmlRes;
+}
+
+/**
+ * Do not insert or remove any resources while executing in this function. It will
+ * corrupt the iteration order.
+ */
+bool ResourceFileFlattener::flatten(ResourceTable* table, IArchiveWriter* archiveWriter) {
+ bool error = false;
+ std::map<std::pair<ConfigDescription, StringPiece16>, FileOperation> configSortedFiles;
+
+ for (auto& pkg : table->packages) {
+ for (auto& type : pkg->types) {
+ // Sort by config and name, so that we get better locality in the zip file.
+ configSortedFiles.clear();
+ for (auto& entry : type->entries) {
+ // Iterate via indices because auto generated values can be inserted ahead of
+ // the value being processed.
+ for (size_t i = 0; i < entry->values.size(); i++) {
+ ResourceConfigValue* configValue = entry->values[i].get();
+
+ FileReference* fileRef = valueCast<FileReference>(configValue->value.get());
+ if (!fileRef) {
+ continue;
+ }
+
+ io::IFile* file = fileRef->file;
+ if (!file) {
+ mContext->getDiagnostics()->error(DiagMessage(fileRef->getSource())
+ << "file not found");
+ return false;
+ }
+
+ FileOperation fileOp;
+ fileOp.dstPath = util::utf16ToUtf8(*fileRef->path);
+
+ const StringPiece srcPath = file->getSource().path;
+ if (type->type != ResourceType::kRaw &&
+ (util::stringEndsWith<char>(srcPath, ".xml.flat") ||
+ util::stringEndsWith<char>(srcPath, ".xml"))) {
+ ResourceFile fileDesc;
+ fileDesc.config = configValue->config;
+ fileDesc.name = ResourceName(pkg->name, type->type, entry->name);
+ fileDesc.source = fileRef->getSource();
+ fileOp.xmlToFlatten = linkAndVersionXmlFile(entry.get(), fileDesc,
+ file, table);
+ if (!fileOp.xmlToFlatten) {
+ error = true;
+ continue;
+ }
+
+ } else {
+ fileOp.fileToCopy = file;
+ }
+
+ // NOTE(adamlesinski): Explicitly construct a StringPiece16 here, or else
+ // we end up copying the string in the std::make_pair() method, then creating
+ // a StringPiece16 from the copy, which would cause us to end up referencing
+ // garbage in the map.
+ const StringPiece16 entryName(entry->name);
+ configSortedFiles[std::make_pair(configValue->config, entryName)] =
+ std::move(fileOp);
+ }
+ }
+
+ if (error) {
+ return false;
+ }
+
+ // Now flatten the sorted values.
+ for (auto& mapEntry : configSortedFiles) {
+ const ConfigDescription& config = mapEntry.first.first;
+ const FileOperation& fileOp = mapEntry.second;
+
+ if (fileOp.xmlToFlatten) {
+ Maybe<size_t> maxSdkLevel;
+ if (!mOptions.noAutoVersion) {
+ maxSdkLevel = std::max<size_t>(config.sdkVersion, 1u);
+ }
+
+ bool result = flattenXml(fileOp.xmlToFlatten.get(), fileOp.dstPath, maxSdkLevel,
+ mOptions.keepRawValues,
+ archiveWriter, mContext);
+ if (!result) {
+ error = true;
+ }
+ } else {
+ bool result = copyFileToArchive(fileOp.fileToCopy, fileOp.dstPath,
+ getCompressionFlags(fileOp.dstPath),
+ archiveWriter, mContext);
+ if (!result) {
+ error = true;
+ }
+ }
+ }
+ }
+ }
+ return !error;
+}
+
class LinkCommand {
public:
LinkCommand(LinkContext* context, const LinkOptions& options) :
@@ -123,7 +492,7 @@
std::unique_ptr<ISymbolTable> createSymbolTableFromIncludePaths() {
AssetManagerSymbolTableBuilder builder;
for (const std::string& path : mOptions.includePaths) {
- if (mOptions.verbose) {
+ if (mContext->verbose()) {
mContext->getDiagnostics()->note(DiagMessage(path) << "loading include path");
}
@@ -140,125 +509,6 @@
return builder.build();
}
- std::unique_ptr<ResourceTable> loadTable(const Source& source, const void* data, size_t len) {
- std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
- BinaryResourceParser parser(mContext, table.get(), source, data, len);
- if (!parser.parse()) {
- return {};
- }
- return table;
- }
-
- std::unique_ptr<ResourceTable> loadTableFromPb(const Source& source,
- const void* data, size_t len) {
- pb::ResourceTable pbTable;
- if (!pbTable.ParseFromArray(data, len)) {
- mContext->getDiagnostics()->error(DiagMessage(source) << "invalid compiled table");
- return {};
- }
-
- std::unique_ptr<ResourceTable> table = deserializeTableFromPb(pbTable, source,
- mContext->getDiagnostics());
- if (!table) {
- return {};
- }
- return table;
- }
-
- /**
- * Inflates an XML file from the source path.
- */
- static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) {
- std::ifstream fin(path, std::ifstream::binary);
- if (!fin) {
- diag->error(DiagMessage(path) << strerror(errno));
- return {};
- }
-
- return xml::inflate(&fin, diag, Source(path));
- }
-
- static std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(
- const Source& source,
- const void* data, size_t len,
- IDiagnostics* diag) {
- CompiledFileInputStream inputStream(data, len);
- if (!inputStream.CompiledFile()) {
- diag->error(DiagMessage(source) << "invalid compiled file header");
- return {};
- }
-
- const uint8_t* xmlData = reinterpret_cast<const uint8_t*>(inputStream.data());
- const size_t xmlDataLen = inputStream.size();
-
- std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(xmlData, xmlDataLen, diag, source);
- if (!xmlRes) {
- return {};
- }
- return xmlRes;
- }
-
- static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source,
- const void* data, size_t len,
- IDiagnostics* diag) {
- CompiledFileInputStream inputStream(data, len);
- const pb::CompiledFile* pbFile = inputStream.CompiledFile();
- if (!pbFile) {
- diag->error(DiagMessage(source) << "invalid compiled file header");
- return {};
- }
-
- std::unique_ptr<ResourceFile> resFile = deserializeCompiledFileFromPb(*pbFile, source,
- diag);
- if (!resFile) {
- return {};
- }
- return resFile;
- }
-
- uint32_t getCompressionFlags(const StringPiece& str) {
- if (mOptions.doNotCompressAnything) {
- return 0;
- }
-
- for (const std::string& extension : mOptions.extensionsToNotCompress) {
- if (util::stringEndsWith<char>(str, extension)) {
- return 0;
- }
- }
- return ArchiveEntry::kCompress;
- }
-
- bool copyFileToArchive(io::IFile* file, const std::string& outPath,
- IArchiveWriter* writer) {
- std::unique_ptr<io::IData> data = file->openAsData();
- if (!data) {
- mContext->getDiagnostics()->error(DiagMessage(file->getSource())
- << "failed to open file");
- return false;
- }
-
- CompiledFileInputStream inputStream(data->data(), data->size());
- if (!inputStream.CompiledFile()) {
- mContext->getDiagnostics()->error(DiagMessage(file->getSource())
- << "invalid compiled file header");
- return false;
- }
-
- if (writer->startEntry(outPath, getCompressionFlags(outPath))) {
- if (writer->writeEntry(reinterpret_cast<const uint8_t*>(inputStream.data()),
- inputStream.size())) {
- if (writer->finishEntry()) {
- return true;
- }
- }
- }
-
- mContext->getDiagnostics()->error(
- DiagMessage(mOptions.outputPath) << "failed to write file " << outPath);
- return false;
- }
-
Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes) {
// Make sure the first element is <manifest> with package attribute.
if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) {
@@ -349,28 +599,6 @@
return false;
}
- bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
- IArchiveWriter* writer) {
- BigBuffer buffer(1024);
- XmlFlattenerOptions options = {};
- options.keepRawValues = mOptions.staticLib;
- options.maxSdkLevel = maxSdkLevel;
- XmlFlattener flattener(&buffer, options);
- if (!flattener.consume(mContext, xmlRes)) {
- return false;
- }
-
- if (writer->startEntry(path, ArchiveEntry::kCompress)) {
- if (writer->writeEntry(buffer)) {
- if (writer->finishEntry()) {
- return true;
- }
- }
- }
- mContext->getDiagnostics()->error(
- DiagMessage() << "failed to write " << path << " to archive");
- return false;
- }
bool writeJavaFile(ResourceTable* table, const StringPiece16& packageNameToGenerate,
const StringPiece16& outPackage, JavaClassGeneratorOptions javaOptions) {
@@ -456,7 +684,7 @@
}
bool mergeResourceTable(io::IFile* file, bool override) {
- if (mOptions.verbose) {
+ if (mContext->verbose()) {
mContext->getDiagnostics()->note(DiagMessage() << "linking " << file->getSource());
}
@@ -467,8 +695,9 @@
return false;
}
- std::unique_ptr<ResourceTable> table = loadTableFromPb(file->getSource(), data->data(),
- data->size());
+ std::unique_ptr<ResourceTable> table = loadTableFromPb(file->getSource(),
+ data->data(), data->size(),
+ mContext->getDiagnostics());
if (!table) {
return false;
}
@@ -483,7 +712,7 @@
}
bool mergeCompiledFile(io::IFile* file, std::unique_ptr<ResourceFile> fileDesc, bool overlay) {
- if (mOptions.verbose) {
+ if (mContext->verbose()) {
mContext->getDiagnostics()->note(DiagMessage() << "adding " << file->getSource());
}
@@ -628,10 +857,9 @@
TableMergerOptions tableMergerOptions;
tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay;
- tableMergerOptions.filter = mOptions.configFilter;
mTableMerger = util::make_unique<TableMerger>(mContext, &mFinalTable, tableMergerOptions);
- if (mOptions.verbose) {
+ if (mContext->verbose()) {
mContext->getDiagnostics()->note(
DiagMessage() << "linking package '" << mContext->mCompilationPackage << "' "
<< "with package ID " << std::hex << (int) mContext->mPackageId);
@@ -692,6 +920,15 @@
mContext->getDiagnostics()->error(DiagMessage() << "failed stripping products");
return 1;
}
+
+ // TODO(adamlesinski): Actually pass in split constraints and handle splits at the file
+ // level.
+ TableSplitter tableSplitter({}, mOptions.tableSplitterOptions);
+ if (!tableSplitter.verifySplitConstraints(mContext)) {
+ return 1;
+ }
+
+ tableSplitter.splitTable(&mFinalTable);
}
proguard::KeepSet proguardKeepSet;
@@ -728,8 +965,10 @@
}
}
- if (!flattenXml(manifestXml.get(), "AndroidManifest.xml", {},
- archiveWriter.get())) {
+ const bool keepRawValues = mOptions.staticLib;
+ bool result = flattenXml(manifestXml.get(), "AndroidManifest.xml", {},
+ keepRawValues, archiveWriter.get(), mContext);
+ if (!result) {
error = true;
}
} else {
@@ -742,113 +981,14 @@
return 1;
}
- for (auto& mergeEntry : mTableMerger->getFilesToMerge()) {
- const ResourceKeyRef& key = mergeEntry.first;
- const FileToMerge& fileToMerge = mergeEntry.second;
+ ResourceFileFlattenerOptions fileFlattenerOptions;
+ fileFlattenerOptions.keepRawValues = mOptions.staticLib;
+ fileFlattenerOptions.doNotCompressAnything = mOptions.doNotCompressAnything;
+ fileFlattenerOptions.extensionsToNotCompress = mOptions.extensionsToNotCompress;
+ fileFlattenerOptions.noAutoVersion = mOptions.noAutoVersion;
+ ResourceFileFlattener fileFlattener(fileFlattenerOptions, mContext, &proguardKeepSet);
- const StringPiece path = fileToMerge.file->getSource().path;
-
- if (key.name.type != ResourceType::kRaw &&
- (util::stringEndsWith<char>(path, ".xml.flat") ||
- util::stringEndsWith<char>(path, ".xml"))) {
- if (mOptions.verbose) {
- mContext->getDiagnostics()->note(DiagMessage() << "linking " << path);
- }
-
- io::IFile* file = fileToMerge.file;
- std::unique_ptr<io::IData> data = file->openAsData();
- if (!data) {
- mContext->getDiagnostics()->error(DiagMessage(file->getSource())
- << "failed to open file");
- return 1;
- }
-
- std::unique_ptr<xml::XmlResource> xmlRes;
- if (util::stringEndsWith<char>(path, ".flat")) {
- xmlRes = loadBinaryXmlSkipFileExport(file->getSource(),
- data->data(), data->size(),
- mContext->getDiagnostics());
- } else {
- xmlRes = xml::inflate(data->data(), data->size(), mContext->getDiagnostics(),
- file->getSource());
- }
-
- if (!xmlRes) {
- return 1;
- }
-
- // Create the file description header.
- xmlRes->file = ResourceFile{
- key.name.toResourceName(),
- key.config,
- fileToMerge.originalSource,
- };
-
- XmlReferenceLinker xmlLinker;
- if (xmlLinker.consume(mContext, xmlRes.get())) {
- if (!proguard::collectProguardRules(xmlRes->file.source, xmlRes.get(),
- &proguardKeepSet)) {
- error = true;
- }
-
- Maybe<size_t> maxSdkLevel;
- if (!mOptions.noAutoVersion) {
- maxSdkLevel = std::max<size_t>(xmlRes->file.config.sdkVersion, 1u);
- }
-
- if (!flattenXml(xmlRes.get(), fileToMerge.dstPath, maxSdkLevel,
- archiveWriter.get())) {
- error = true;
- }
-
- if (!mOptions.noAutoVersion) {
- Maybe<ResourceTable::SearchResult> result = mFinalTable.findResource(
- xmlRes->file.name);
- for (int sdkLevel : xmlLinker.getSdkLevels()) {
- if (sdkLevel > xmlRes->file.config.sdkVersion &&
- shouldGenerateVersionedResource(result.value().entry,
- xmlRes->file.config,
- sdkLevel)) {
- xmlRes->file.config.sdkVersion = sdkLevel;
-
- std::string genResourcePath = ResourceUtils::buildResourceFileName(
- xmlRes->file, mContext->getNameMangler());
-
- bool added = mFinalTable.addFileReference(
- xmlRes->file.name,
- xmlRes->file.config,
- xmlRes->file.source,
- util::utf8ToUtf16(genResourcePath),
- mContext->getDiagnostics());
- if (!added) {
- error = true;
- continue;
- }
-
- if (!flattenXml(xmlRes.get(), genResourcePath, sdkLevel,
- archiveWriter.get())) {
- error = true;
- }
- }
- }
- }
-
- } else {
- error = true;
- }
- } else {
- if (mOptions.verbose) {
- mContext->getDiagnostics()->note(DiagMessage() << "copying " << path);
- }
-
- if (!copyFileToArchive(fileToMerge.file, fileToMerge.dstPath,
- archiveWriter.get())) {
- error = true;
- }
- }
- }
-
- if (error) {
+ if (!fileFlattener.flatten(&mFinalTable, archiveWriter.get())) {
mContext->getDiagnostics()->error(DiagMessage() << "failed linking file resources");
return 1;
}
@@ -912,8 +1052,10 @@
}
}
- if (mOptions.verbose) {
- Debug::printTable(&mFinalTable);
+ if (mContext->verbose()) {
+ DebugPrintTableOptions debugPrintTableOptions;
+ debugPrintTableOptions.showSources = true;
+ Debug::printTable(&mFinalTable, debugPrintTableOptions);
}
return 0;
}
@@ -934,6 +1076,7 @@
};
int link(const std::vector<StringPiece>& args) {
+ LinkContext context;
LinkOptions options;
Maybe<std::string> privateSymbolsPackage;
Maybe<std::string> minSdkVersion, targetSdkVersion;
@@ -942,6 +1085,7 @@
Maybe<std::string> customJavaPackage;
std::vector<std::string> extraJavaPackages;
Maybe<std::string> configs;
+ Maybe<std::string> preferredDensity;
Maybe<std::string> productList;
bool legacyXFlag = false;
bool requireLocalization = false;
@@ -966,6 +1110,9 @@
&requireLocalization)
.optionalFlag("-c", "Comma separated list of configurations to include. The default\n"
"is all configurations", &configs)
+ .optionalFlag("--preferred-density",
+ "Selects the closest matching density and strips out all others.",
+ &preferredDensity)
.optionalFlag("--product", "Comma separated list of product names to keep",
&productList)
.optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
@@ -1001,14 +1148,12 @@
&renameInstrumentationTargetPackage)
.optionalFlagList("-0", "File extensions not to compress",
&options.extensionsToNotCompress)
- .optionalSwitch("-v", "Enables verbose logging", &options.verbose);
+ .optionalSwitch("-v", "Enables verbose logging", &context.mVerbose);
if (!flags.parse("aapt2 link", args, &std::cerr)) {
return 1;
}
- LinkContext context;
-
if (privateSymbolsPackage) {
options.privateSymbols = util::utf8ToUtf16(privateSymbolsPackage.value());
}
@@ -1082,7 +1227,29 @@
}
}
- options.configFilter = &filter;
+ options.tableSplitterOptions.configFilter = &filter;
+ }
+
+ if (preferredDensity) {
+ ConfigDescription preferredDensityConfig;
+ if (!ConfigDescription::parse(preferredDensity.value(), &preferredDensityConfig)) {
+ context.getDiagnostics()->error(DiagMessage() << "invalid density '"
+ << preferredDensity.value()
+ << "' for --preferred-density option");
+ return 1;
+ }
+
+ // Clear the version that can be automatically added.
+ preferredDensityConfig.sdkVersion = 0;
+
+ if (preferredDensityConfig.diff(ConfigDescription::defaultConfig())
+ != ConfigDescription::CONFIG_DENSITY) {
+ context.getDiagnostics()->error(DiagMessage() << "invalid preferred density '"
+ << preferredDensity.value() << "'. "
+ << "Preferred density must only be a density value");
+ return 1;
+ }
+ options.tableSplitterOptions.preferredDensity = preferredDensityConfig.density;
}
LinkCommand cmd(&context, options);
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index 2ecd5b0..5f11745 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -98,8 +98,7 @@
return false;
}
- mFilesToMerge[ResourceKeyRef(name, config)] = FileToMerge{
- f, oldFile->getSource(), util::utf16ToUtf8(*newFile->path) };
+ newFile->file = f;
return true;
};
@@ -198,10 +197,6 @@
for (auto& srcValue : srcEntry->values) {
ResourceConfigValue* dstValue = dstEntry->findValue(srcValue->config,
srcValue->product);
-
- const bool stripConfig = mOptions.filter ?
- !mOptions.filter->match(srcValue->config) : false;
-
if (dstValue) {
const int collisionResult = ResourceTable::resolveValueCollision(
dstValue->value.get(), srcValue->value.get());
@@ -227,10 +222,6 @@
}
- if (stripConfig) {
- continue;
- }
-
if (!dstValue) {
// Force create the entry if we didn't have it.
dstValue = dstEntry->findOrCreateValue(srcValue->config, srcValue->product);
@@ -286,6 +277,7 @@
std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>(
table.stringPool.makeRef(path));
fileRef->setSource(fileDesc.source);
+ fileRef->file = file;
ResourceTablePackage* pkg = table.createPackage(fileDesc.name.package, 0x0);
pkg->findOrCreateType(fileDesc.name.type)
@@ -293,15 +285,8 @@
->findOrCreateValue(fileDesc.config, {})
->value = std::move(fileRef);
- auto callback = [&](const ResourceNameRef& name, const ConfigDescription& config,
- FileReference* newFile, FileReference* oldFile) -> bool {
- mFilesToMerge[ResourceKeyRef(name, config)] = FileToMerge{
- file, oldFile->getSource(), util::utf16ToUtf8(*newFile->path) };
- return true;
- };
-
return doMerge(file->getSource(), &table, pkg,
- false /* mangle */, overlay /* overlay */, true /* allow new */, callback);
+ false /* mangle */, overlay /* overlay */, true /* allow new */, {});
}
bool TableMerger::mergeFile(const ResourceFile& fileDesc, io::IFile* file) {
diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h
index 4539679..b3c22dd 100644
--- a/tools/aapt2/link/TableMerger.h
+++ b/tools/aapt2/link/TableMerger.h
@@ -30,33 +30,11 @@
namespace aapt {
-struct FileToMerge {
- /**
- * The compiled file from which to read the data.
- */
- io::IFile* file;
-
- /**
- * Where the original, uncompiled file came from.
- */
- Source originalSource;
-
- /**
- * The destination path within the APK/archive.
- */
- std::string dstPath;
-};
-
struct TableMergerOptions {
/**
* If true, resources in overlays can be added without previously having existed.
*/
bool autoAddOverlay = false;
-
- /**
- * A filter that removes resources whose configurations don't match.
- */
- IConfigFilter* filter = nullptr;
};
/**
@@ -81,10 +59,6 @@
*/
TableMerger(IAaptContext* context, ResourceTable* outTable, const TableMergerOptions& options);
- const std::map<ResourceKeyRef, FileToMerge>& getFilesToMerge() {
- return mFilesToMerge;
- }
-
const std::set<std::u16string>& getMergedPackages() const {
return mMergedPackages;
}
@@ -119,8 +93,7 @@
private:
using FileMergeCallback = std::function<bool(const ResourceNameRef&,
const ConfigDescription& config,
- FileReference*,
- FileReference*)>;
+ FileReference*, FileReference*)>;
IAaptContext* mContext;
ResourceTable* mMasterTable;
@@ -128,7 +101,6 @@
ResourceTablePackage* mMasterPackage;
std::set<std::u16string> mMergedPackages;
- std::map<ResourceKeyRef, FileToMerge> mFilesToMerge;
bool mergeFileImpl(const ResourceFile& fileDesc, io::IFile* file, bool overlay);
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
index 45c8c98..4a80d3f 100644
--- a/tools/aapt2/link/TableMerger_test.cpp
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -97,16 +97,6 @@
test::parseConfigOrDie("hdpi-v4"));
ASSERT_NE(nullptr, file);
EXPECT_EQ(std::u16string(u"res/layout-hdpi-v4/main.xml"), *file->path);
-
- ResourceName name = test::parseNameOrDie(u"@com.app.a:layout/main");
- ResourceKeyRef key = { name, test::parseConfigOrDie("hdpi-v4") };
-
- auto iter = merger.getFilesToMerge().find(key);
- ASSERT_NE(merger.getFilesToMerge().end(), iter);
-
- const FileToMerge& actualFileToMerge = iter->second;
- EXPECT_EQ(&testFile, actualFileToMerge.file);
- EXPECT_EQ(std::string("res/layout-hdpi-v4/main.xml"), actualFileToMerge.dstPath);
}
TEST_F(TableMergerTest, MergeFileOverlay) {
@@ -122,14 +112,6 @@
ASSERT_TRUE(merger.mergeFile(fileDesc, &fileA));
ASSERT_TRUE(merger.mergeFileOverlay(fileDesc, &fileB));
-
- ResourceName name = test::parseNameOrDie(u"@com.app.a:xml/foo");
- ResourceKeyRef key = { name, ConfigDescription{} };
- auto iter = merger.getFilesToMerge().find(key);
- ASSERT_NE(merger.getFilesToMerge().end(), iter);
-
- const FileToMerge& actualFileToMerge = iter->second;
- EXPECT_EQ(&fileB, actualFileToMerge.file);
}
TEST_F(TableMergerTest, MergeFileReferences) {
@@ -157,15 +139,6 @@
f = test::getValue<FileReference>(&finalTable, u"@com.app.a:xml/com.app.b$file");
ASSERT_NE(f, nullptr);
EXPECT_EQ(std::u16string(u"res/xml/com.app.b$file.xml"), *f->path);
-
- ResourceName name = test::parseNameOrDie(u"@com.app.a:xml/com.app.b$file");
- ResourceKeyRef key = { name, ConfigDescription{} };
- auto iter = merger.getFilesToMerge().find(key);
- ASSERT_NE(merger.getFilesToMerge().end(), iter);
-
- const FileToMerge& actualFileToMerge = iter->second;
- EXPECT_EQ(Source("res/xml/file.xml"), actualFileToMerge.file->getSource());
- EXPECT_EQ(std::string("res/xml/com.app.b$file.xml"), actualFileToMerge.dstPath);
}
TEST_F(TableMergerTest, OverrideResourceWithOverlay) {
@@ -244,36 +217,4 @@
ASSERT_FALSE(merger.mergeOverlay({}, tableB.get()));
}
-TEST_F(TableMergerTest, MergeAndStripResourcesNotMatchingFilter) {
- ResourceTable finalTable;
- TableMergerOptions options;
- options.autoAddOverlay = false;
-
- AxisConfigFilter filter;
- filter.addConfig(test::parseConfigOrDie("en"));
- options.filter = &filter;
-
- test::TestFile fileA("res/layout-en/main.xml"), fileB("res/layout-fr-rFR/main.xml");
- const ResourceName name = test::parseNameOrDie(u"@com.app.a:layout/main");
- const ConfigDescription configEn = test::parseConfigOrDie("en");
- const ConfigDescription configFr = test::parseConfigOrDie("fr-rFR");
-
- TableMerger merger(mContext.get(), &finalTable, options);
- ASSERT_TRUE(merger.mergeFile(ResourceFile{ name, configEn }, &fileA));
- ASSERT_TRUE(merger.mergeFile(ResourceFile{ name, configFr }, &fileB));
-
- EXPECT_NE(nullptr, test::getValueForConfig<FileReference>(&finalTable,
- u"@com.app.a:layout/main",
- configEn));
- EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(&finalTable,
- u"@com.app.a:layout/main",
- configFr));
-
- EXPECT_NE(merger.getFilesToMerge().end(),
- merger.getFilesToMerge().find(ResourceKeyRef(name, configEn)));
-
- EXPECT_EQ(merger.getFilesToMerge().end(),
- merger.getFilesToMerge().find(ResourceKeyRef(name, configFr)));
-}
-
} // namespace aapt
diff --git a/tools/aapt2/process/IResourceTableConsumer.h b/tools/aapt2/process/IResourceTableConsumer.h
index a2528d2..3a88044 100644
--- a/tools/aapt2/process/IResourceTableConsumer.h
+++ b/tools/aapt2/process/IResourceTableConsumer.h
@@ -40,6 +40,7 @@
virtual StringPiece16 getCompilationPackage() = 0;
virtual uint8_t getPackageId() = 0;
virtual NameMangler* getNameMangler() = 0;
+ virtual bool verbose() = 0;
};
struct IResourceTableConsumer {
diff --git a/tools/aapt2/split/TableSplitter.cpp b/tools/aapt2/split/TableSplitter.cpp
new file mode 100644
index 0000000..0f7649b
--- /dev/null
+++ b/tools/aapt2/split/TableSplitter.cpp
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ConfigDescription.h"
+#include "ResourceTable.h"
+#include "split/TableSplitter.h"
+
+#include <map>
+#include <set>
+#include <unordered_map>
+#include <vector>
+
+namespace aapt {
+
+using ConfigClaimedMap = std::unordered_map<ResourceConfigValue*, bool>;
+using ConfigDensityGroups = std::map<ConfigDescription, std::vector<ResourceConfigValue*>>;
+
+static ConfigDescription copyWithoutDensity(const ConfigDescription& config) {
+ ConfigDescription withoutDensity = config;
+ withoutDensity.density = 0;
+ return withoutDensity;
+}
+
+/**
+ * Selects values that match exactly the constraints given.
+ */
+class SplitValueSelector {
+public:
+ SplitValueSelector(const SplitConstraints& constraints) {
+ for (const ConfigDescription& config : constraints.configs) {
+ if (config.density == 0) {
+ mDensityIndependentConfigs.insert(config);
+ } else {
+ mDensityDependentConfigToDensityMap[copyWithoutDensity(config)] = config.density;
+ }
+ }
+ }
+
+ std::vector<ResourceConfigValue*> selectValues(const ConfigDensityGroups& densityGroups,
+ ConfigClaimedMap* claimedValues) {
+ std::vector<ResourceConfigValue*> selected;
+
+ // Select the regular values.
+ for (auto& entry : *claimedValues) {
+ // Check if the entry has a density.
+ ResourceConfigValue* configValue = entry.first;
+ if (configValue->config.density == 0 && !entry.second) {
+ // This is still available.
+ if (mDensityIndependentConfigs.find(configValue->config) !=
+ mDensityIndependentConfigs.end()) {
+ selected.push_back(configValue);
+
+ // Mark the entry as taken.
+ entry.second = true;
+ }
+ }
+ }
+
+ // Now examine the densities
+ for (auto& entry : densityGroups) {
+ // We do not care if the value is claimed, since density values can be
+ // in multiple splits.
+ const ConfigDescription& config = entry.first;
+ const std::vector<ResourceConfigValue*>& relatedValues = entry.second;
+
+ auto densityValueIter = mDensityDependentConfigToDensityMap.find(config);
+ if (densityValueIter != mDensityDependentConfigToDensityMap.end()) {
+ // Select the best one!
+ ConfigDescription targetDensity = config;
+ targetDensity.density = densityValueIter->second;
+
+ ResourceConfigValue* bestValue = nullptr;
+ for (ResourceConfigValue* thisValue : relatedValues) {
+ if (!bestValue ||
+ thisValue->config.isBetterThan(bestValue->config, &targetDensity)) {
+ bestValue = thisValue;
+ }
+
+ // When we select one of these, they are all claimed such that the base
+ // doesn't include any anymore.
+ (*claimedValues)[thisValue] = true;
+ }
+ assert(bestValue);
+ selected.push_back(bestValue);
+ }
+ }
+ return selected;
+ }
+
+private:
+ std::set<ConfigDescription> mDensityIndependentConfigs;
+ std::map<ConfigDescription, uint16_t> mDensityDependentConfigToDensityMap;
+};
+
+/**
+ * Marking non-preferred densities as claimed will make sure the base doesn't include them,
+ * leaving only the preferred density behind.
+ */
+static void markNonPreferredDensitiesAsClaimed(uint16_t preferredDensity,
+ const ConfigDensityGroups& densityGroups,
+ ConfigClaimedMap* configClaimedMap) {
+ for (auto& entry : densityGroups) {
+ const ConfigDescription& config = entry.first;
+ const std::vector<ResourceConfigValue*>& relatedValues = entry.second;
+
+ ConfigDescription targetDensity = config;
+ targetDensity.density = preferredDensity;
+ ResourceConfigValue* bestValue = nullptr;
+ for (ResourceConfigValue* thisValue : relatedValues) {
+ if (!bestValue) {
+ bestValue = thisValue;
+ } else if (thisValue->config.isBetterThan(bestValue->config, &targetDensity)) {
+ // Claim the previous value so that it is not included in the base.
+ (*configClaimedMap)[bestValue] = true;
+ bestValue = thisValue;
+ } else {
+ // Claim this value so that it is not included in the base.
+ (*configClaimedMap)[thisValue] = true;
+ }
+ }
+ assert(bestValue);
+ }
+}
+
+bool TableSplitter::verifySplitConstraints(IAaptContext* context) {
+ bool error = false;
+ for (size_t i = 0; i < mSplitConstraints.size(); i++) {
+ for (size_t j = i + 1; j < mSplitConstraints.size(); j++) {
+ for (const ConfigDescription& config : mSplitConstraints[i].configs) {
+ if (mSplitConstraints[j].configs.find(config) !=
+ mSplitConstraints[j].configs.end()) {
+ context->getDiagnostics()->error(DiagMessage() << "config '" << config
+ << "' appears in multiple splits, "
+ << "target split ambiguous");
+ error = true;
+ }
+ }
+ }
+ }
+ return !error;
+}
+
+void TableSplitter::splitTable(ResourceTable* originalTable) {
+ const size_t splitCount = mSplitConstraints.size();
+ for (auto& pkg : originalTable->packages) {
+ // Initialize all packages for splits.
+ for (size_t idx = 0; idx < splitCount; idx++) {
+ ResourceTable* splitTable = mSplits[idx].get();
+ splitTable->createPackage(pkg->name, pkg->id);
+ }
+
+ for (auto& type : pkg->types) {
+ if (type->type == ResourceType::kMipmap) {
+ // Always keep mipmaps.
+ continue;
+ }
+
+ for (auto& entry : type->entries) {
+ if (mConfigFilter) {
+ // First eliminate any resource that we definitely don't want.
+ for (std::unique_ptr<ResourceConfigValue>& configValue : entry->values) {
+ if (!mConfigFilter->match(configValue->config)) {
+ // null out the entry. We will clean up and remove nulls at the end
+ // for performance reasons.
+ configValue.reset();
+ }
+ }
+ }
+
+ // Organize the values into two separate buckets. Those that are density-dependent
+ // and those that are density-independent.
+ // One density technically matches all density, it's just that some densities
+ // match better. So we need to be aware of the full set of densities to make this
+ // decision.
+ ConfigDensityGroups densityGroups;
+ ConfigClaimedMap configClaimedMap;
+ for (const std::unique_ptr<ResourceConfigValue>& configValue : entry->values) {
+ if (configValue) {
+ configClaimedMap[configValue.get()] = false;
+
+ if (configValue->config.density != 0) {
+ // Create a bucket for this density-dependent config.
+ densityGroups[copyWithoutDensity(configValue->config)]
+ .push_back(configValue.get());
+ }
+ }
+ }
+
+ // First we check all the splits. If it doesn't match one of the splits, we
+ // leave it in the base.
+ for (size_t idx = 0; idx < splitCount; idx++) {
+ const SplitConstraints& splitConstraint = mSplitConstraints[idx];
+ ResourceTable* splitTable = mSplits[idx].get();
+
+ // Select the values we want from this entry for this split.
+ SplitValueSelector selector(splitConstraint);
+ std::vector<ResourceConfigValue*> selectedValues =
+ selector.selectValues(densityGroups, &configClaimedMap);
+
+ // No need to do any work if we selected nothing.
+ if (!selectedValues.empty()) {
+ // Create the same resource structure in the split. We do this lazily
+ // because we might not have actual values for each type/entry.
+ ResourceTablePackage* splitPkg = splitTable->findPackage(pkg->name);
+ ResourceTableType* splitType = splitPkg->findOrCreateType(type->type);
+ if (!splitType->id) {
+ splitType->id = type->id;
+ splitType->symbolStatus = type->symbolStatus;
+ }
+
+ ResourceEntry* splitEntry = splitType->findOrCreateEntry(entry->name);
+ if (!splitEntry->id) {
+ splitEntry->id = entry->id;
+ splitEntry->symbolStatus = entry->symbolStatus;
+ }
+
+ // Copy the selected values into the new Split Entry.
+ for (ResourceConfigValue* configValue : selectedValues) {
+ ResourceConfigValue* newConfigValue = splitEntry->findOrCreateValue(
+ configValue->config, configValue->product);
+ newConfigValue->value = std::unique_ptr<Value>(
+ configValue->value->clone(&splitTable->stringPool));
+ }
+ }
+ }
+
+ if (mPreferredDensity) {
+ markNonPreferredDensitiesAsClaimed(mPreferredDensity.value(),
+ densityGroups,
+ &configClaimedMap);
+ }
+
+ // All splits are handled, now check to see what wasn't claimed and remove
+ // whatever exists in other splits.
+ for (std::unique_ptr<ResourceConfigValue>& configValue : entry->values) {
+ if (configValue && configClaimedMap[configValue.get()]) {
+ // Claimed, remove from base.
+ configValue.reset();
+ }
+ }
+
+ // Now erase all nullptrs.
+ entry->values.erase(
+ std::remove(entry->values.begin(), entry->values.end(), nullptr),
+ entry->values.end());
+ }
+ }
+ }
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/split/TableSplitter.h b/tools/aapt2/split/TableSplitter.h
new file mode 100644
index 0000000..15e0764
--- /dev/null
+++ b/tools/aapt2/split/TableSplitter.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_SPLIT_TABLESPLITTER_H
+#define AAPT_SPLIT_TABLESPLITTER_H
+
+#include "ConfigDescription.h"
+#include "ResourceTable.h"
+#include "filter/ConfigFilter.h"
+#include "process/IResourceTableConsumer.h"
+
+#include <android-base/macros.h>
+#include <set>
+#include <vector>
+
+namespace aapt {
+
+struct SplitConstraints {
+ std::set<ConfigDescription> configs;
+};
+
+struct TableSplitterOptions {
+ /**
+ * The preferred density to keep in the table, stripping out all others.
+ */
+ Maybe<uint16_t> preferredDensity;
+
+ /**
+ * Configuration filter that determines which resource configuration values end up in
+ * the final table.
+ */
+ IConfigFilter* configFilter = nullptr;
+};
+
+class TableSplitter {
+public:
+ TableSplitter(const std::vector<SplitConstraints>& splits,
+ const TableSplitterOptions& options) :
+ mSplitConstraints(splits), mPreferredDensity(options.preferredDensity),
+ mConfigFilter(options.configFilter) {
+ for (size_t i = 0; i < mSplitConstraints.size(); i++) {
+ mSplits.push_back(util::make_unique<ResourceTable>());
+ }
+ }
+
+ bool verifySplitConstraints(IAaptContext* context);
+
+ void splitTable(ResourceTable* originalTable);
+
+ const std::vector<std::unique_ptr<ResourceTable>>& getSplits() {
+ return mSplits;
+ }
+
+private:
+ std::vector<SplitConstraints> mSplitConstraints;
+ std::vector<std::unique_ptr<ResourceTable>> mSplits;
+ Maybe<uint16_t> mPreferredDensity;
+ IConfigFilter* mConfigFilter;
+
+ DISALLOW_COPY_AND_ASSIGN(TableSplitter);
+};
+
+}
+
+#endif /* AAPT_SPLIT_TABLESPLITTER_H */
diff --git a/tools/aapt2/split/TableSplitter_test.cpp b/tools/aapt2/split/TableSplitter_test.cpp
new file mode 100644
index 0000000..74ca32e
--- /dev/null
+++ b/tools/aapt2/split/TableSplitter_test.cpp
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "split/TableSplitter.h"
+#include "test/Builders.h"
+#include "test/Common.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(TableSplitterTest, NoSplitPreferredDensity) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .addFileReference(u"@android:drawable/icon", u"res/drawable-mdpi/icon.png",
+ test::parseConfigOrDie("mdpi"))
+ .addFileReference(u"@android:drawable/icon", u"res/drawable-hdpi/icon.png",
+ test::parseConfigOrDie("hdpi"))
+ .addFileReference(u"@android:drawable/icon", u"res/drawable-xhdpi/icon.png",
+ test::parseConfigOrDie("xhdpi"))
+ .addFileReference(u"@android:drawable/icon", u"res/drawable-xxhdpi/icon.png",
+ test::parseConfigOrDie("xxhdpi"))
+ .addSimple(u"@android:string/one", {})
+ .build();
+
+ TableSplitterOptions options;
+ options.preferredDensity = ConfigDescription::DENSITY_XHIGH;
+ TableSplitter splitter({}, options);
+ splitter.splitTable(table.get());
+
+ EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(table.get(),
+ u"@android:drawable/icon",
+ test::parseConfigOrDie("mdpi")));
+ EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(table.get(),
+ u"@android:drawable/icon",
+ test::parseConfigOrDie("hdpi")));
+ EXPECT_NE(nullptr, test::getValueForConfig<FileReference>(table.get(),
+ u"@android:drawable/icon",
+ test::parseConfigOrDie("xhdpi")));
+ EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(table.get(),
+ u"@android:drawable/icon",
+ test::parseConfigOrDie("xxhdpi")));
+ EXPECT_NE(nullptr, test::getValue<Id>(table.get(), u"@android:string/one"));
+}
+
+TEST(TableSplitterTest, SplitTableByConfigAndDensity) {
+ ResourceTable table;
+
+ const ResourceName foo = test::parseNameOrDie(u"@android:string/foo");
+ ASSERT_TRUE(table.addResource(foo, test::parseConfigOrDie("land-hdpi"), {},
+ util::make_unique<Id>(),
+ test::getDiagnostics()));
+ ASSERT_TRUE(table.addResource(foo, test::parseConfigOrDie("land-xhdpi"), {},
+ util::make_unique<Id>(),
+ test::getDiagnostics()));
+ ASSERT_TRUE(table.addResource(foo, test::parseConfigOrDie("land-xxhdpi"), {},
+ util::make_unique<Id>(),
+ test::getDiagnostics()));
+
+ std::vector<SplitConstraints> constraints;
+ constraints.push_back(SplitConstraints{ { test::parseConfigOrDie("land-mdpi") } });
+ constraints.push_back(SplitConstraints{ { test::parseConfigOrDie("land-xhdpi") } });
+
+ TableSplitter splitter(constraints, TableSplitterOptions{});
+ splitter.splitTable(&table);
+
+ ASSERT_EQ(2u, splitter.getSplits().size());
+
+ ResourceTable* splitOne = splitter.getSplits()[0].get();
+ ResourceTable* splitTwo = splitter.getSplits()[1].get();
+
+ // Since a split was defined, all densities should be gone from base.
+ EXPECT_EQ(nullptr, test::getValueForConfig<Id>(&table, u"@android:string/foo",
+ test::parseConfigOrDie("land-hdpi")));
+ EXPECT_EQ(nullptr, test::getValueForConfig<Id>(&table, u"@android:string/foo",
+ test::parseConfigOrDie("land-xhdpi")));
+ EXPECT_EQ(nullptr, test::getValueForConfig<Id>(&table, u"@android:string/foo",
+ test::parseConfigOrDie("land-xxhdpi")));
+
+ EXPECT_NE(nullptr, test::getValueForConfig<Id>(splitOne, u"@android:string/foo",
+ test::parseConfigOrDie("land-hdpi")));
+ EXPECT_EQ(nullptr, test::getValueForConfig<Id>(splitOne, u"@android:string/foo",
+ test::parseConfigOrDie("land-xhdpi")));
+ EXPECT_EQ(nullptr, test::getValueForConfig<Id>(splitOne, u"@android:string/foo",
+ test::parseConfigOrDie("land-xxhdpi")));
+
+ EXPECT_EQ(nullptr, test::getValueForConfig<Id>(splitTwo, u"@android:string/foo",
+ test::parseConfigOrDie("land-hdpi")));
+ EXPECT_NE(nullptr, test::getValueForConfig<Id>(splitTwo, u"@android:string/foo",
+ test::parseConfigOrDie("land-xhdpi")));
+ EXPECT_EQ(nullptr, test::getValueForConfig<Id>(splitTwo, u"@android:string/foo",
+ test::parseConfigOrDie("land-xxhdpi")));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h
index 555a539..e540cd7 100644
--- a/tools/aapt2/test/Context.h
+++ b/tools/aapt2/test/Context.h
@@ -71,6 +71,10 @@
assert(mNameMangler && "test name mangler not set");
return mNameMangler.get();
}
+
+ bool verbose() override {
+ return false;
+ }
};
class ContextBuilder {
diff --git a/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml b/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml
index 5bb3e3e..3681f2a 100644
--- a/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml
+++ b/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="UTF-8"?>
<component name="InspectionProjectProfileManager">
<profile version="1.0" is_locked="false">
<option name="myName" value="Project Default" />
@@ -8,6 +7,15 @@
<option name="CHECK_TRY_CATCH_SECTION" value="true" />
<option name="CHECK_METHOD_BODY" value="true" />
</inspection_tool>
+ <inspection_tool class="LoggerInitializedWithForeignClass" enabled="false" level="WARNING" enabled_by_default="false">
+ <option name="loggerClassName" value="org.apache.log4j.Logger,org.slf4j.LoggerFactory,org.apache.commons.logging.LogFactory,java.util.logging.Logger" />
+ <option name="loggerFactoryMethodName" value="getLogger,getLogger,getLog,getLogger" />
+ </inspection_tool>
<inspection_tool class="ToArrayCallWithZeroLengthArrayArgument" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="WeakerAccess" enabled="true" level="WARNING" enabled_by_default="true">
+ <option name="SUGGEST_PACKAGE_LOCAL_FOR_MEMBERS" value="false" />
+ <option name="SUGGEST_PACKAGE_LOCAL_FOR_TOP_CLASSES" value="false" />
+ <option name="SUGGEST_PRIVATE_FOR_INNERS" value="true" />
+ </inspection_tool>
</profile>
</component>
\ No newline at end of file
diff --git a/tools/layoutlib/.idea/misc.xml b/tools/layoutlib/.idea/misc.xml
index b474bdc..44b47f2 100644
--- a/tools/layoutlib/.idea/misc.xml
+++ b/tools/layoutlib/.idea/misc.xml
@@ -37,7 +37,7 @@
</value>
</option>
</component>
- <component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" default="false" assert-keyword="true" jdk-15="true" project-jdk-name="1.6" project-jdk-type="JavaSDK">
+ <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
\ No newline at end of file
diff --git a/tools/layoutlib/.idea/runConfigurations/Bridge_quick.xml b/tools/layoutlib/.idea/runConfigurations/Bridge_quick.xml
index 4f0eb8d..b402849 100644
--- a/tools/layoutlib/.idea/runConfigurations/Bridge_quick.xml
+++ b/tools/layoutlib/.idea/runConfigurations/Bridge_quick.xml
@@ -26,4 +26,4 @@
<ConfigurationWrapper RunnerId="Run" />
<method />
</configuration>
-</component>
\ No newline at end of file
+</component>
diff --git a/tools/layoutlib/Android.mk b/tools/layoutlib/Android.mk
index 53bfc15..c2ad9ef 100644
--- a/tools/layoutlib/Android.mk
+++ b/tools/layoutlib/Android.mk
@@ -16,7 +16,7 @@
LOCAL_PATH := $(my-dir)
include $(CLEAR_VARS)
-LOCAL_JAVACFLAGS := -source 6 -target 6
+LOCAL_JAVA_LANGUAGE_VERSION := 1.8
#
# Define rules to build temp_layoutlib.jar, which contains a subset of
diff --git a/tools/layoutlib/bridge/Android.mk b/tools/layoutlib/bridge/Android.mk
index 0dbdd56..16e5913 100644
--- a/tools/layoutlib/bridge/Android.mk
+++ b/tools/layoutlib/bridge/Android.mk
@@ -18,8 +18,7 @@
LOCAL_SRC_FILES := $(call all-java-files-under,src)
LOCAL_JAVA_RESOURCE_DIRS := resources
-LOCAL_JAVACFLAGS := -source 6 -target 6
-
+LOCAL_JAVA_LANGUAGE_VERSION := 1.8
LOCAL_JAVA_LIBRARIES := \
layoutlib_api-prebuilt \
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java b/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java
index 0e39243..fe46480 100644
--- a/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java
+++ b/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java
@@ -49,6 +49,7 @@
import java.io.InputStream;
import java.util.Iterator;
+@SuppressWarnings("deprecation")
public final class BridgeResources extends Resources {
private BridgeContext mContext;
@@ -70,6 +71,7 @@
@Override
public boolean markSupported() {
+ //noinspection SimplifiableIfStatement
if (mFakeMarkSupport) {
// this is needed so that BitmapFactory doesn't wrap this in a BufferedInputStream.
return true;
@@ -595,7 +597,6 @@
if (value != null) {
ResourceValue resValue = value.getSecond();
- assert resValue != null;
if (resValue != null) {
String v = resValue.getValue();
if (v != null) {
diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
index 0737682..e3bb3e3 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
@@ -17,9 +17,14 @@
package android.graphics;
import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceValue;
import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.layoutlib.bridge.impl.RenderAction;
import com.android.resources.Density;
+import com.android.resources.ResourceType;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import android.annotation.Nullable;
@@ -38,6 +43,7 @@
import java.util.Set;
import javax.imageio.ImageIO;
+import libcore.util.NativeAllocationRegistry_Delegate;
/**
* Delegate implementing the native methods of android.graphics.Bitmap
@@ -62,12 +68,13 @@
// ---- delegate manager ----
private static final DelegateManager<Bitmap_Delegate> sManager =
new DelegateManager<Bitmap_Delegate>(Bitmap_Delegate.class);
+ private static long sFinalizer = -1;
// ---- delegate helper data ----
// ---- delegate data ----
private final Config mConfig;
- private BufferedImage mImage;
+ private final BufferedImage mImage;
private boolean mHasAlpha = true;
private boolean mHasMipMap = false; // TODO: check the default.
private boolean mIsPremultiplied = true;
@@ -114,10 +121,25 @@
* @see Bitmap#isMutable()
* @see Bitmap#getDensity()
*/
- public static Bitmap createBitmap(File input, Set<BitmapCreateFlags> createFlags,
+ private static Bitmap createBitmap(File input, Set<BitmapCreateFlags> createFlags,
Density density) throws IOException {
// create a delegate with the content of the file.
- Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input), Config.ARGB_8888);
+ BufferedImage image = ImageIO.read(input);
+ if (image == null && input.exists()) {
+ // There was a problem decoding the image, or the decoder isn't registered. Webp maybe.
+ // Replace with a broken image icon.
+ BridgeContext currentContext = RenderAction.getCurrentContext();
+ if (currentContext != null) {
+ RenderResources resources = currentContext.getRenderResources();
+ ResourceValue broken = resources.getFrameworkResource(ResourceType.DRAWABLE,
+ "ic_menu_report_image");
+ File brokenFile = new File(broken.getValue());
+ if (brokenFile.exists()) {
+ image = ImageIO.read(brokenFile);
+ }
+ }
+ }
+ Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ARGB_8888);
return createBitmap(delegate, createFlags, density.getDpiValue());
}
@@ -281,8 +303,13 @@
}
@LayoutlibDelegate
- /*package*/ static void nativeDestructor(long nativeBitmap) {
- sManager.removeJavaReferenceFor(nativeBitmap);
+ /*package*/ static long nativeGetNativeFinalizer() {
+ synchronized (Bitmap_Delegate.class) {
+ if (sFinalizer == -1) {
+ sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(sManager::removeJavaReferenceFor);
+ }
+ return sFinalizer;
+ }
}
@LayoutlibDelegate
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
index ba0d399..c4fbd56 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
@@ -21,6 +21,7 @@
import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.layoutlib.bridge.impl.GcSnapshot;
import com.android.layoutlib.bridge.impl.PorterDuffUtility;
+import com.android.ninepatch.NinePatchChunk;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import android.annotation.Nullable;
@@ -38,6 +39,8 @@
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
+import libcore.util.NativeAllocationRegistry_Delegate;
+
/**
* Delegate implementing the native methods of android.graphics.Canvas
@@ -57,6 +60,7 @@
// ---- delegate manager ----
private static final DelegateManager<Canvas_Delegate> sManager =
new DelegateManager<Canvas_Delegate>(Canvas_Delegate.class);
+ private static long sFinalizer = -1;
// ---- delegate helper data ----
@@ -160,6 +164,9 @@
}
@LayoutlibDelegate
+ /*package*/ static void native_setHighContrastText(long nativeCanvas, boolean highContrastText){}
+
+ @LayoutlibDelegate
/*package*/ static int native_getWidth(long nativeCanvas) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
@@ -749,6 +756,61 @@
}
@LayoutlibDelegate
+ /*package*/ static void native_drawRegion(long nativeCanvas, long nativeRegion,
+ long nativePaint) {
+ // FIXME
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Some canvas paths may not be drawn", null, null);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_drawNinePatch(Canvas thisCanvas, long nativeCanvas,
+ long nativeBitmap, long ninePatch, final float dstLeft, final float dstTop,
+ final float dstRight, final float dstBottom, long nativePaintOrZero,
+ final int screenDensity, final int bitmapDensity) {
+
+ // get the delegate from the native int.
+ final Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmap);
+ if (bitmapDelegate == null) {
+ return;
+ }
+
+ byte[] c = NinePatch_Delegate.getChunk(ninePatch);
+ if (c == null) {
+ // not a 9-patch?
+ BufferedImage image = bitmapDelegate.getImage();
+ drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, 0, 0, image.getWidth(),
+ image.getHeight(), (int) dstLeft, (int) dstTop, (int) dstRight,
+ (int) dstBottom);
+ return;
+ }
+
+ final NinePatchChunk chunkObject = NinePatch_Delegate.getChunk(c);
+ assert chunkObject != null;
+ if (chunkObject == null) {
+ return;
+ }
+
+ Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
+ if (canvasDelegate == null) {
+ return;
+ }
+
+ // this one can be null
+ Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero);
+
+ canvasDelegate.getSnapshot().draw(new GcSnapshot.Drawable() {
+ @Override
+ public void draw(Graphics2D graphics, Paint_Delegate paint) {
+ chunkObject.draw(bitmapDelegate.getImage(), graphics, (int) dstLeft, (int) dstTop,
+ (int) (dstRight - dstLeft), (int) (dstBottom - dstTop), screenDensity,
+ bitmapDensity);
+ }
+ }, paintDelegate, true, false);
+
+ }
+
+ @LayoutlibDelegate
/*package*/ static void native_drawBitmap(Canvas thisCanvas, long nativeCanvas, Bitmap bitmap,
float left, float top,
long nativePaintOrZero,
@@ -934,20 +996,21 @@
}
@LayoutlibDelegate
- /*package*/ static void finalizer(long nativeCanvas) {
- // get the delegate from the native int so that it can be disposed.
- Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
- if (canvasDelegate == null) {
- return;
+ /*package*/ static long getNativeFinalizer() {
+ synchronized (Canvas_Delegate.class) {
+ if (sFinalizer == -1) {
+ sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(nativePtr -> {
+ Canvas_Delegate delegate = sManager.getDelegate(nativePtr);
+ if (delegate != null) {
+ delegate.dispose();
+ }
+ sManager.removeJavaReferenceFor(nativePtr);
+ });
+ }
}
-
- canvasDelegate.dispose();
-
- // remove it from the manager.
- sManager.removeJavaReferenceFor(nativeCanvas);
+ return sFinalizer;
}
-
// ---- Private delegate/helper methods ----
/**
diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
index b6588b6..746ef36 100644
--- a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
@@ -33,13 +33,13 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
import java.util.Scanner;
import java.util.Set;
@@ -73,7 +73,7 @@
private static final Map<String, FontInfo> sCache =
new LinkedHashMap<String, FontInfo>(CACHE_SIZE) {
@Override
- protected boolean removeEldestEntry(Entry<String, FontInfo> eldest) {
+ protected boolean removeEldestEntry(Map.Entry<String, FontInfo> eldest) {
return size() > CACHE_SIZE;
}
@@ -213,7 +213,7 @@
return mValid;
}
- /*package*/ static Font loadFont(String path) {
+ private static Font loadFont(String path) {
if (path.startsWith(SYSTEM_FONTS) ) {
String relativePath = path.substring(SYSTEM_FONTS.length());
File f = new File(sFontLocation, relativePath);
@@ -270,16 +270,12 @@
}
@LayoutlibDelegate
- /*package*/ static boolean nAddFont(long nativeFamily, final String path) {
+ /*package*/ static boolean nAddFont(long nativeFamily, final String path, int ttcIndex) {
+ // FIXME: support ttc fonts. Hack JRE??
final FontFamily_Delegate delegate = getDelegate(nativeFamily);
if (delegate != null) {
if (sFontLocation == null) {
- delegate.mPostInitRunnables.add(new Runnable() {
- @Override
- public void run() {
- delegate.addFont(path);
- }
- });
+ delegate.mPostInitRunnables.add(() -> delegate.addFont(path));
return true;
}
return delegate.addFont(path);
@@ -289,18 +285,18 @@
@LayoutlibDelegate
/*package*/ static boolean nAddFontWeightStyle(long nativeFamily,
- final String path, final int index, final List<FontListParser.Axis> axes,
+ ByteBuffer buffer, final List<FontListParser.Axis> axes,
final int weight, final boolean isItalic) {
- // 'index' and 'axes' are not supported by java.awt.Font
+ assert false : "The only client of this method has been overriden.";
+ return false;
+ }
+
+ static boolean addFont(long nativeFamily, final String path, final int weight,
+ final boolean isItalic) {
final FontFamily_Delegate delegate = getDelegate(nativeFamily);
if (delegate != null) {
if (sFontLocation == null) {
- delegate.mPostInitRunnables.add(new Runnable() {
- @Override
- public void run() {
- delegate.addFont(path, weight, isItalic);
- }
- });
+ delegate.mPostInitRunnables.add(() -> delegate.addFont(path, weight, isItalic));
return true;
}
return delegate.addFont(path, weight, isItalic);
@@ -311,6 +307,9 @@
@LayoutlibDelegate
/*package*/ static boolean nAddFontFromAsset(long nativeFamily, AssetManager mgr, String path) {
FontFamily_Delegate ffd = sManager.getDelegate(nativeFamily);
+ if (ffd == null) {
+ return false;
+ }
ffd.mValid = true;
if (mgr == null) {
return false;
@@ -454,6 +453,7 @@
private FontInfo deriveFont(@NonNull FontInfo srcFont, @NonNull FontInfo outFont) {
int desiredWeight = outFont.mWeight;
int srcWeight = srcFont.mWeight;
+ assert srcFont.mFont != null;
Font derivedFont = srcFont.mFont;
// Embolden the font if required.
if (desiredWeight >= BOLD_FONT_WEIGHT && desiredWeight - srcWeight > BOLD_FONT_WEIGHT_DELTA / 2) {
diff --git a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java
index e8d34d0..1f0eb3b 100644
--- a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java
@@ -169,77 +169,18 @@
sManager.removeJavaReferenceFor(chunk);
}
- @LayoutlibDelegate
- /*package*/ static void nativeDraw(long canvas_instance, RectF loc, Bitmap bitmap_instance,
- long chunk, long paint_instance_or_null, int destDensity, int srcDensity) {
- draw(canvas_instance,
- (int) loc.left, (int) loc.top, (int) loc.right, (int) loc.bottom,
- bitmap_instance, chunk, paint_instance_or_null,
- destDensity, srcDensity);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nativeDraw(long canvas_instance, Rect loc, Bitmap bitmap_instance,
- long chunk, long paint_instance_or_null, int destDensity, int srcDensity) {
- draw(canvas_instance,
- loc.left, loc.top, loc.right, loc.bottom,
- bitmap_instance, chunk, paint_instance_or_null,
- destDensity, srcDensity);
- }
@LayoutlibDelegate
/*package*/ static long nativeGetTransparentRegion(Bitmap bitmap, long chunk, Rect location) {
return 0;
}
- // ---- Private Helper methods ----
-
- private static void draw(long canvas_instance,
- final int left, final int top, final int right, final int bottom,
- Bitmap bitmap_instance, long chunk, long paint_instance_or_null,
- final int destDensity, final int srcDensity) {
- // get the delegate from the native int.
- final Bitmap_Delegate bitmap_delegate = Bitmap_Delegate.getDelegate(bitmap_instance);
- if (bitmap_delegate == null) {
- return;
- }
-
- byte[] c = null;
- NinePatch_Delegate delegate = sManager.getDelegate(chunk);
+ static byte[] getChunk(long nativeNinePatch) {
+ NinePatch_Delegate delegate = sManager.getDelegate(nativeNinePatch);
if (delegate != null) {
- c = delegate.chunk;
+ return delegate.chunk;
}
- if (c == null) {
- // not a 9-patch?
- BufferedImage image = bitmap_delegate.getImage();
- Canvas_Delegate.native_drawBitmap(null, canvas_instance, bitmap_instance,
- 0f, 0f, (float)image.getWidth(), (float)image.getHeight(),
- (float)left, (float)top, (float)right, (float)bottom,
- paint_instance_or_null, destDensity, srcDensity);
- return;
- }
+ return null;
+ }
- final NinePatchChunk chunkObject = getChunk(c);
- assert chunkObject != null;
- if (chunkObject == null) {
- return;
- }
-
- Canvas_Delegate canvas_delegate = Canvas_Delegate.getDelegate(canvas_instance);
- if (canvas_delegate == null) {
- return;
- }
-
- // this one can be null
- Paint_Delegate paint_delegate = Paint_Delegate.getDelegate(paint_instance_or_null);
-
- canvas_delegate.getSnapshot().draw(new GcSnapshot.Drawable() {
- @Override
- public void draw(Graphics2D graphics, Paint_Delegate paint) {
- chunkObject.draw(bitmap_delegate.getImage(), graphics,
- left, top, right - left, bottom - top, destDensity, srcDensity);
- }
- }, paint_delegate, true /*compositeOnly*/, false /*forceSrcMode*/);
-
- }
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
index dbd45c4..514d785 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
@@ -39,6 +39,8 @@
import java.util.List;
import java.util.Locale;
+import libcore.util.NativeAllocationRegistry_Delegate;
+
/**
* Delegate implementing the native methods of android.graphics.Paint
*
@@ -65,6 +67,7 @@
// ---- delegate manager ----
private static final DelegateManager<Paint_Delegate> sManager =
new DelegateManager<Paint_Delegate>(Paint_Delegate.class);
+ private static long sFinalizer = -1;
// ---- delegate helper data ----
@@ -250,9 +253,9 @@
// ---- native methods ----
@LayoutlibDelegate
- /*package*/ static int getFlags(Paint thisPaint) {
+ /*package*/ static int nGetFlags(Paint thisPaint, long nativePaint) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return 0;
}
@@ -263,9 +266,9 @@
@LayoutlibDelegate
- /*package*/ static void setFlags(Paint thisPaint, int flags) {
+ /*package*/ static void nSetFlags(Paint thisPaint, long nativePaint, int flags) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return;
}
@@ -274,14 +277,14 @@
}
@LayoutlibDelegate
- /*package*/ static void setFilterBitmap(Paint thisPaint, boolean filter) {
- setFlag(thisPaint, Paint.FILTER_BITMAP_FLAG, filter);
+ /*package*/ static void nSetFilterBitmap(Paint thisPaint, long nativePaint, boolean filter) {
+ setFlag(nativePaint, Paint.FILTER_BITMAP_FLAG, filter);
}
@LayoutlibDelegate
- /*package*/ static int getHinting(Paint thisPaint) {
+ /*package*/ static int nGetHinting(Paint thisPaint, long nativePaint) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return Paint.HINTING_ON;
}
@@ -290,9 +293,9 @@
}
@LayoutlibDelegate
- /*package*/ static void setHinting(Paint thisPaint, int mode) {
+ /*package*/ static void nSetHinting(Paint thisPaint, long nativePaint, int mode) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return;
}
@@ -301,44 +304,48 @@
}
@LayoutlibDelegate
- /*package*/ static void setAntiAlias(Paint thisPaint, boolean aa) {
- setFlag(thisPaint, Paint.ANTI_ALIAS_FLAG, aa);
+ /*package*/ static void nSetAntiAlias(Paint thisPaint, long nativePaint, boolean aa) {
+ setFlag(nativePaint, Paint.ANTI_ALIAS_FLAG, aa);
}
@LayoutlibDelegate
- /*package*/ static void setSubpixelText(Paint thisPaint, boolean subpixelText) {
- setFlag(thisPaint, Paint.SUBPIXEL_TEXT_FLAG, subpixelText);
+ /*package*/ static void nSetSubpixelText(Paint thisPaint, long nativePaint,
+ boolean subpixelText) {
+ setFlag(nativePaint, Paint.SUBPIXEL_TEXT_FLAG, subpixelText);
}
@LayoutlibDelegate
- /*package*/ static void setUnderlineText(Paint thisPaint, boolean underlineText) {
- setFlag(thisPaint, Paint.UNDERLINE_TEXT_FLAG, underlineText);
+ /*package*/ static void nSetUnderlineText(Paint thisPaint, long nativePaint,
+ boolean underlineText) {
+ setFlag(nativePaint, Paint.UNDERLINE_TEXT_FLAG, underlineText);
}
@LayoutlibDelegate
- /*package*/ static void setStrikeThruText(Paint thisPaint, boolean strikeThruText) {
- setFlag(thisPaint, Paint.STRIKE_THRU_TEXT_FLAG, strikeThruText);
+ /*package*/ static void nSetStrikeThruText(Paint thisPaint, long nativePaint,
+ boolean strikeThruText) {
+ setFlag(nativePaint, Paint.STRIKE_THRU_TEXT_FLAG, strikeThruText);
}
@LayoutlibDelegate
- /*package*/ static void setFakeBoldText(Paint thisPaint, boolean fakeBoldText) {
- setFlag(thisPaint, Paint.FAKE_BOLD_TEXT_FLAG, fakeBoldText);
+ /*package*/ static void nSetFakeBoldText(Paint thisPaint, long nativePaint,
+ boolean fakeBoldText) {
+ setFlag(nativePaint, Paint.FAKE_BOLD_TEXT_FLAG, fakeBoldText);
}
@LayoutlibDelegate
- /*package*/ static void setDither(Paint thisPaint, boolean dither) {
- setFlag(thisPaint, Paint.DITHER_FLAG, dither);
+ /*package*/ static void nSetDither(Paint thisPaint, long nativePaint, boolean dither) {
+ setFlag(nativePaint, Paint.DITHER_FLAG, dither);
}
@LayoutlibDelegate
- /*package*/ static void setLinearText(Paint thisPaint, boolean linearText) {
- setFlag(thisPaint, Paint.LINEAR_TEXT_FLAG, linearText);
+ /*package*/ static void nSetLinearText(Paint thisPaint, long nativePaint, boolean linearText) {
+ setFlag(nativePaint, Paint.LINEAR_TEXT_FLAG, linearText);
}
@LayoutlibDelegate
- /*package*/ static int getColor(Paint thisPaint) {
+ /*package*/ static int nGetColor(Paint thisPaint, long nativePaint) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return 0;
}
@@ -347,9 +354,9 @@
}
@LayoutlibDelegate
- /*package*/ static void setColor(Paint thisPaint, int color) {
+ /*package*/ static void nSetColor(Paint thisPaint, long nativePaint, int color) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return;
}
@@ -358,9 +365,9 @@
}
@LayoutlibDelegate
- /*package*/ static int getAlpha(Paint thisPaint) {
+ /*package*/ static int nGetAlpha(Paint thisPaint, long nativePaint) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return 0;
}
@@ -369,9 +376,9 @@
}
@LayoutlibDelegate
- /*package*/ static void setAlpha(Paint thisPaint, int a) {
+ /*package*/ static void nSetAlpha(Paint thisPaint, long nativePaint, int a) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return;
}
@@ -380,9 +387,9 @@
}
@LayoutlibDelegate
- /*package*/ static float getStrokeWidth(Paint thisPaint) {
+ /*package*/ static float nGetStrokeWidth(Paint thisPaint, long nativePaint) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return 1.f;
}
@@ -391,9 +398,9 @@
}
@LayoutlibDelegate
- /*package*/ static void setStrokeWidth(Paint thisPaint, float width) {
+ /*package*/ static void nSetStrokeWidth(Paint thisPaint, long nativePaint, float width) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return;
}
@@ -402,9 +409,9 @@
}
@LayoutlibDelegate
- /*package*/ static float getStrokeMiter(Paint thisPaint) {
+ /*package*/ static float nGetStrokeMiter(Paint thisPaint, long nativePaint) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return 1.f;
}
@@ -413,9 +420,9 @@
}
@LayoutlibDelegate
- /*package*/ static void setStrokeMiter(Paint thisPaint, float miter) {
+ /*package*/ static void nSetStrokeMiter(Paint thisPaint, long nativePaint, float miter) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return;
}
@@ -424,7 +431,7 @@
}
@LayoutlibDelegate
- /*package*/ static void native_setShadowLayer(long paint, float radius, float dx, float dy,
+ /*package*/ static void nSetShadowLayer(long paint, float radius, float dx, float dy,
int color) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
@@ -432,7 +439,7 @@
}
@LayoutlibDelegate
- /*package*/ static boolean native_hasShadowLayer(long paint) {
+ /*package*/ static boolean nHasShadowLayer(long paint) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
"Paint.hasShadowLayer is not supported.", null, null /*data*/);
@@ -440,16 +447,17 @@
}
@LayoutlibDelegate
- /*package*/ static boolean isElegantTextHeight(Paint thisPaint) {
+ /*package*/ static boolean nIsElegantTextHeight(Paint thisPaint, long nativePaint) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
return delegate != null && delegate.mFontVariant == FontVariant.ELEGANT;
}
@LayoutlibDelegate
- /*package*/ static void setElegantTextHeight(Paint thisPaint, boolean elegant) {
+ /*package*/ static void nSetElegantTextHeight(Paint thisPaint, long nativePaint,
+ boolean elegant) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return;
}
@@ -458,9 +466,9 @@
}
@LayoutlibDelegate
- /*package*/ static float getTextSize(Paint thisPaint) {
+ /*package*/ static float nGetTextSize(Paint thisPaint, long nativePaint) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return 1.f;
}
@@ -469,9 +477,9 @@
}
@LayoutlibDelegate
- /*package*/ static void setTextSize(Paint thisPaint, float textSize) {
+ /*package*/ static void nSetTextSize(Paint thisPaint, long nativePaint, float textSize) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return;
}
@@ -483,9 +491,9 @@
}
@LayoutlibDelegate
- /*package*/ static float getTextScaleX(Paint thisPaint) {
+ /*package*/ static float nGetTextScaleX(Paint thisPaint, long nativePaint) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return 1.f;
}
@@ -494,9 +502,9 @@
}
@LayoutlibDelegate
- /*package*/ static void setTextScaleX(Paint thisPaint, float scaleX) {
+ /*package*/ static void nSetTextScaleX(Paint thisPaint, long nativePaint, float scaleX) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return;
}
@@ -508,9 +516,9 @@
}
@LayoutlibDelegate
- /*package*/ static float getTextSkewX(Paint thisPaint) {
+ /*package*/ static float nGetTextSkewX(Paint thisPaint, long nativePaint) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return 1.f;
}
@@ -519,9 +527,9 @@
}
@LayoutlibDelegate
- /*package*/ static void setTextSkewX(Paint thisPaint, float skewX) {
+ /*package*/ static void nSetTextSkewX(Paint thisPaint, long nativePaint, float skewX) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return;
}
@@ -533,9 +541,9 @@
}
@LayoutlibDelegate
- /*package*/ static float ascent(Paint thisPaint) {
+ /*package*/ static float nAscent(Paint thisPaint, long nativePaint, long nativeTypeface) {
// get the delegate
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return 0;
}
@@ -550,9 +558,9 @@
}
@LayoutlibDelegate
- /*package*/ static float descent(Paint thisPaint) {
+ /*package*/ static float nDescent(Paint thisPaint, long nativePaint, long nativeTypeface) {
// get the delegate
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return 0;
}
@@ -567,9 +575,10 @@
}
@LayoutlibDelegate
- /*package*/ static float getFontMetrics(Paint thisPaint, FontMetrics metrics) {
+ /*package*/ static float nGetFontMetrics(Paint thisPaint, long nativePaint, long nativeTypeface,
+ FontMetrics metrics) {
// get the delegate
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return 0;
}
@@ -578,9 +587,10 @@
}
@LayoutlibDelegate
- /*package*/ static int getFontMetricsInt(Paint thisPaint, FontMetricsInt fmi) {
+ /*package*/ static int nGetFontMetricsInt(Paint thisPaint, long nativePaint,
+ long nativeTypeface, FontMetricsInt fmi) {
// get the delegate
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return 0;
}
@@ -603,31 +613,7 @@
}
@LayoutlibDelegate
- /*package*/ static float native_measureText(Paint thisPaint, char[] text, int index,
- int count, int bidiFlags) {
- // get the delegate
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
- if (delegate == null) {
- return 0;
- }
-
- RectF bounds = delegate.measureText(text, index, count, null, 0, bidiFlags);
- return bounds.right - bounds.left;
- }
-
- @LayoutlibDelegate
- /*package*/ static float native_measureText(Paint thisPaint, String text, int start, int end,
- int bidiFlags) {
- return native_measureText(thisPaint, text.toCharArray(), start, end - start, bidiFlags);
- }
-
- @LayoutlibDelegate
- /*package*/ static float native_measureText(Paint thisPaint, String text, int bidiFlags) {
- return native_measureText(thisPaint, text.toCharArray(), 0, text.length(), bidiFlags);
- }
-
- @LayoutlibDelegate
- /*package*/ static int native_breakText(long nativePaint, long nativeTypeface, char[] text,
+ /*package*/ static int nBreakText(long nativePaint, long nativeTypeface, char[] text,
int index, int count, float maxWidth, int bidiFlags, float[] measuredWidth) {
// get the delegate
@@ -669,21 +655,21 @@
}
@LayoutlibDelegate
- /*package*/ static int native_breakText(long nativePaint, long nativeTypeface, String text,
+ /*package*/ static int nBreakText(long nativePaint, long nativeTypeface, String text,
boolean measureForwards,
float maxWidth, int bidiFlags, float[] measuredWidth) {
- return native_breakText(nativePaint, nativeTypeface, text.toCharArray(), 0, text.length(),
+ return nBreakText(nativePaint, nativeTypeface, text.toCharArray(), 0, text.length(),
maxWidth, bidiFlags, measuredWidth);
}
@LayoutlibDelegate
- /*package*/ static long native_init() {
+ /*package*/ static long nInit() {
Paint_Delegate newDelegate = new Paint_Delegate();
return sManager.addNewDelegate(newDelegate);
}
@LayoutlibDelegate
- /*package*/ static long native_initWithPaint(long paint) {
+ /*package*/ static long nInitWithPaint(long paint) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(paint);
if (delegate == null) {
@@ -695,7 +681,7 @@
}
@LayoutlibDelegate
- /*package*/ static void native_reset(long native_object) {
+ /*package*/ static void nReset(long native_object) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
@@ -706,7 +692,7 @@
}
@LayoutlibDelegate
- /*package*/ static void native_set(long native_dst, long native_src) {
+ /*package*/ static void nSet(long native_dst, long native_src) {
// get the delegate from the native int.
Paint_Delegate delegate_dst = sManager.getDelegate(native_dst);
if (delegate_dst == null) {
@@ -723,7 +709,7 @@
}
@LayoutlibDelegate
- /*package*/ static int native_getStyle(long native_object) {
+ /*package*/ static int nGetStyle(long native_object) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
@@ -734,7 +720,7 @@
}
@LayoutlibDelegate
- /*package*/ static void native_setStyle(long native_object, int style) {
+ /*package*/ static void nSetStyle(long native_object, int style) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
@@ -745,7 +731,7 @@
}
@LayoutlibDelegate
- /*package*/ static int native_getStrokeCap(long native_object) {
+ /*package*/ static int nGetStrokeCap(long native_object) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
@@ -756,7 +742,7 @@
}
@LayoutlibDelegate
- /*package*/ static void native_setStrokeCap(long native_object, int cap) {
+ /*package*/ static void nSetStrokeCap(long native_object, int cap) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
@@ -767,7 +753,7 @@
}
@LayoutlibDelegate
- /*package*/ static int native_getStrokeJoin(long native_object) {
+ /*package*/ static int nGetStrokeJoin(long native_object) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
@@ -778,7 +764,7 @@
}
@LayoutlibDelegate
- /*package*/ static void native_setStrokeJoin(long native_object, int join) {
+ /*package*/ static void nSetStrokeJoin(long native_object, int join) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
@@ -789,7 +775,7 @@
}
@LayoutlibDelegate
- /*package*/ static boolean native_getFillPath(long native_object, long src, long dst) {
+ /*package*/ static boolean nGetFillPath(long native_object, long src, long dst) {
Paint_Delegate paint = sManager.getDelegate(native_object);
if (paint == null) {
return false;
@@ -815,7 +801,7 @@
}
@LayoutlibDelegate
- /*package*/ static long native_setShader(long native_object, long shader) {
+ /*package*/ static long nSetShader(long native_object, long shader) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
@@ -828,7 +814,7 @@
}
@LayoutlibDelegate
- /*package*/ static long native_setColorFilter(long native_object, long filter) {
+ /*package*/ static long nSetColorFilter(long native_object, long filter) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
@@ -847,7 +833,7 @@
}
@LayoutlibDelegate
- /*package*/ static long native_setXfermode(long native_object, long xfermode) {
+ /*package*/ static long nSetXfermode(long native_object, long xfermode) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
@@ -860,7 +846,7 @@
}
@LayoutlibDelegate
- /*package*/ static long native_setPathEffect(long native_object, long effect) {
+ /*package*/ static long nSetPathEffect(long native_object, long effect) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
@@ -873,7 +859,7 @@
}
@LayoutlibDelegate
- /*package*/ static long native_setMaskFilter(long native_object, long maskfilter) {
+ /*package*/ static long nSetMaskFilter(long native_object, long maskfilter) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
@@ -892,7 +878,7 @@
}
@LayoutlibDelegate
- /*package*/ static long native_setTypeface(long native_object, long typeface) {
+ /*package*/ static long nSetTypeface(long native_object, long typeface) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
@@ -909,7 +895,7 @@
}
@LayoutlibDelegate
- /*package*/ static long native_setRasterizer(long native_object, long rasterizer) {
+ /*package*/ static long nSetRasterizer(long native_object, long rasterizer) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
@@ -928,7 +914,7 @@
}
@LayoutlibDelegate
- /*package*/ static int native_getTextAlign(long native_object) {
+ /*package*/ static int nGetTextAlign(long native_object) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
@@ -939,7 +925,7 @@
}
@LayoutlibDelegate
- /*package*/ static void native_setTextAlign(long native_object, int align) {
+ /*package*/ static void nSetTextAlign(long native_object, int align) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
@@ -950,58 +936,27 @@
}
@LayoutlibDelegate
- /*package*/ static void native_setTextLocale(long native_object, String locale) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(native_object);
- if (delegate == null) {
- return;
- }
-
- delegate.setTextLocale(locale);
- }
-
- @LayoutlibDelegate
- /*package*/ static int native_getTextWidths(long native_object, long native_typeface,
- char[] text, int index, int count, int bidiFlags, float[] widths) {
-
- if (widths != null) {
- for (int i = 0; i< count; i++) {
- widths[i]=0;
- }
- }
+ /*package*/ static int nSetTextLocales(long native_object, String locale) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
return 0;
}
- // native_typeface is passed here since Framework's old implementation did not have the
- // typeface object associated with the Paint. Since, we follow the new framework way,
- // we store the typeface with the paint and use it directly.
- assert (native_typeface == delegate.mNativeTypeface);
-
- RectF bounds = delegate.measureText(text, index, count, widths, 0, bidiFlags);
- return ((int) (bounds.right - bounds.left));
- }
-
- @LayoutlibDelegate
- /*package*/ static int native_getTextWidths(long native_object, long native_typeface,
- String text, int start, int end, int bidiFlags, float[] widths) {
- return native_getTextWidths(native_object, native_typeface, text.toCharArray(), start,
- end - start, bidiFlags, widths);
- }
-
- @LayoutlibDelegate
- /* package */static int native_getTextGlyphs(long native_object, String text, int start,
- int end, int contextStart, int contextEnd, int flags, char[] glyphs) {
- // FIXME
+ delegate.setTextLocale(locale);
return 0;
}
@LayoutlibDelegate
- /*package*/ static float native_getTextRunAdvances(long native_object, long native_typeface,
+ /*package*/ static void nSetTextLocalesByMinikinLangListId(long paintPtr,
+ int mMinikinLangListId) {
+ // FIXME
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float nGetTextAdvances(long native_object, long native_typeface,
char[] text, int index, int count, int contextIndex, int contextCount,
- boolean isRtl, float[] advances, int advancesIndex) {
+ int bidiFlags, float[] advances, int advancesIndex) {
if (advances != null)
for (int i = advancesIndex; i< advancesIndex+count; i++)
@@ -1017,25 +972,25 @@
// we store the typeface with the paint and use it directly.
assert (native_typeface == delegate.mNativeTypeface);
- RectF bounds = delegate.measureText(text, index, count, advances, advancesIndex, isRtl);
+ RectF bounds = delegate.measureText(text, index, count, advances, advancesIndex, bidiFlags);
return bounds.right - bounds.left;
}
@LayoutlibDelegate
- /*package*/ static float native_getTextRunAdvances(long native_object, long native_typeface,
+ /*package*/ static float nGetTextAdvances(long native_object, long native_typeface,
String text, int start, int end, int contextStart, int contextEnd,
- boolean isRtl, float[] advances, int advancesIndex) {
+ int bidiFlags, float[] advances, int advancesIndex) {
// FIXME: support contextStart and contextEnd
int count = end - start;
char[] buffer = TemporaryBuffer.obtain(count);
TextUtils.getChars(text, start, end, buffer, 0);
- return native_getTextRunAdvances(native_object, native_typeface, buffer, 0, count,
- contextStart, contextEnd - contextStart, isRtl, advances, advancesIndex);
+ return nGetTextAdvances(native_object, native_typeface, buffer, 0, count,
+ contextStart, contextEnd - contextStart, bidiFlags, advances, advancesIndex);
}
@LayoutlibDelegate
- /*package*/ static int native_getTextRunCursor(Paint thisPaint, long native_object, char[] text,
+ /*package*/ static int nGetTextRunCursor(Paint thisPaint, long native_object, char[] text,
int contextStart, int contextLength, int flags, int offset, int cursorOpt) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
@@ -1044,7 +999,7 @@
}
@LayoutlibDelegate
- /*package*/ static int native_getTextRunCursor(Paint thisPaint, long native_object, String text,
+ /*package*/ static int nGetTextRunCursor(Paint thisPaint, long native_object, String text,
int contextStart, int contextEnd, int flags, int offset, int cursorOpt) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
@@ -1053,7 +1008,7 @@
}
@LayoutlibDelegate
- /*package*/ static void native_getTextPath(long native_object, long native_typeface,
+ /*package*/ static void nGetTextPath(long native_object, long native_typeface,
int bidiFlags, char[] text, int index, int count, float x, float y, long path) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
@@ -1061,7 +1016,7 @@
}
@LayoutlibDelegate
- /*package*/ static void native_getTextPath(long native_object, long native_typeface,
+ /*package*/ static void nGetTextPath(long native_object, long native_typeface,
int bidiFlags, String text, int start, int end, float x, float y, long path) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
@@ -1069,14 +1024,14 @@
}
@LayoutlibDelegate
- /*package*/ static void nativeGetStringBounds(long nativePaint, long native_typeface,
+ /*package*/ static void nGetStringBounds(long nativePaint, long native_typeface,
String text, int start, int end, int bidiFlags, Rect bounds) {
- nativeGetCharArrayBounds(nativePaint, native_typeface, text.toCharArray(), start,
+ nGetCharArrayBounds(nativePaint, native_typeface, text.toCharArray(), start,
end - start, bidiFlags, bounds);
}
@LayoutlibDelegate
- /*package*/ static void nativeGetCharArrayBounds(long nativePaint, long native_typeface,
+ /*package*/ static void nGetCharArrayBounds(long nativePaint, long native_typeface,
char[] text, int index, int count, int bidiFlags, Rect bounds) {
// get the delegate from the native int.
@@ -1092,12 +1047,18 @@
}
@LayoutlibDelegate
- /*package*/ static void finalizer(long nativePaint) {
- sManager.removeJavaReferenceFor(nativePaint);
+ /*package*/ static long nGetNativeFinalizer() {
+ synchronized (Paint_Delegate.class) {
+ if (sFinalizer == -1) {
+ sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(
+ sManager::removeJavaReferenceFor);
+ }
+ }
+ return sFinalizer;
}
@LayoutlibDelegate
- /*package*/ static float native_getLetterSpacing(long nativePaint) {
+ /*package*/ static float nGetLetterSpacing(long nativePaint) {
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return 0;
@@ -1106,7 +1067,7 @@
}
@LayoutlibDelegate
- /*package*/ static void native_setLetterSpacing(long nativePaint, float letterSpacing) {
+ /*package*/ static void nSetLetterSpacing(long nativePaint, float letterSpacing) {
Bridge.getLog().fidelityWarning(LayoutLog.TAG_TEXT_RENDERING,
"Paint.setLetterSpacing() not supported.", null, null);
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
@@ -1117,13 +1078,13 @@
}
@LayoutlibDelegate
- /*package*/ static void native_setFontFeatureSettings(long nativePaint, String settings) {
+ /*package*/ static void nSetFontFeatureSettings(long nativePaint, String settings) {
Bridge.getLog().fidelityWarning(LayoutLog.TAG_TEXT_RENDERING,
"Paint.setFontFeatureSettings() not supported.", null, null);
}
@LayoutlibDelegate
- /*package*/ static int native_getHyphenEdit(long nativePaint) {
+ /*package*/ static int nGetHyphenEdit(long nativePaint) {
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return 0;
@@ -1132,7 +1093,7 @@
}
@LayoutlibDelegate
- /*package*/ static void native_setHyphenEdit(long nativePaint, int hyphen) {
+ /*package*/ static void nSetHyphenEdit(long nativePaint, int hyphen) {
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return;
@@ -1141,7 +1102,7 @@
}
@LayoutlibDelegate
- /*package*/ static boolean native_hasGlyph(long nativePaint, long nativeTypeface, int bidiFlags,
+ /*package*/ static boolean nHasGlyph(long nativePaint, long nativeTypeface, int bidiFlags,
String string) {
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
@@ -1169,13 +1130,14 @@
@LayoutlibDelegate
- /*package*/ static float native_getRunAdvance(long nativePaint, long nativeTypeface,
+ /*package*/ static float nGetRunAdvance(long nativePaint, long nativeTypeface,
@NonNull char[] text, int start, int end, int contextStart, int contextEnd,
boolean isRtl, int offset) {
int count = end - start;
float[] advances = new float[count];
- native_getTextRunAdvances(nativePaint, nativeTypeface, text, start, count,
- contextStart, contextEnd - contextStart, isRtl, advances, 0);
+ int bidiFlags = isRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR;
+ nGetTextAdvances(nativePaint, nativeTypeface, text, start, count,
+ contextStart, contextEnd - contextStart, bidiFlags, advances, 0);
int startOffset = offset - start; // offset from start.
float sum = 0;
for (int i = 0; i < startOffset; i++) {
@@ -1185,13 +1147,14 @@
}
@LayoutlibDelegate
- /*package*/ static int native_getOffsetForAdvance(long nativePaint, long nativeTypeface,
+ /*package*/ static int nGetOffsetForAdvance(long nativePaint, long nativeTypeface,
char[] text, int start, int end, int contextStart, int contextEnd, boolean isRtl,
float advance) {
int count = end - start;
float[] advances = new float[count];
- native_getTextRunAdvances(nativePaint, nativeTypeface, text, start, count,
- contextStart, contextEnd - contextStart, isRtl, advances, 0);
+ int bidiFlags = isRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR;
+ nGetTextAdvances(nativePaint, nativeTypeface, text, start, count,
+ contextStart, contextEnd - contextStart, bidiFlags, advances, 0);
float sum = 0;
int i;
for (i = 0; i < count && sum < advance; i++) {
@@ -1359,9 +1322,9 @@
mLocale = new Locale(locale);
}
- private static void setFlag(Paint thisPaint, int flagMask, boolean flagValue) {
+ private static void setFlag(long nativePaint, int flagMask, boolean flagValue) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return;
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
index a10ac00..e1da27b 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
@@ -75,7 +75,7 @@
return sManager.getDelegate(nPath);
}
- public Shape getJavaShape() {
+ public Path2D getJavaShape() {
return mPath;
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
index 85e65e6..5cd34f6 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
@@ -24,8 +24,10 @@
import java.awt.Font;
import java.io.File;
+import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import static android.graphics.FontFamily_Delegate.getFontLocation;
@@ -205,6 +207,17 @@
return new File(getFontLocation());
}
+ @LayoutlibDelegate
+ /*package*/ static FontFamily makeFamilyFromParsed(FontListParser.Family family,
+ Map<String, ByteBuffer> bufferForPath) {
+ FontFamily fontFamily = new FontFamily(family.lang, family.variant);
+ for (FontListParser.Font font : family.fonts) {
+ FontFamily_Delegate.addFont(fontFamily.mNativePtr, font.fontName, font.weight,
+ font.isItalic);
+ }
+ return fontFamily;
+ }
+
// ---- Private delegate/helper methods ----
private Typeface_Delegate(@NonNull FontFamily_Delegate[] fontFamilies, int style) {
diff --git a/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java b/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java
index 44ce731..fcd63ea 100644
--- a/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java
@@ -25,7 +25,7 @@
/**
* Delegate that overrides implementation for certain methods in {@link android.text.Hyphenator}
* <p/>
- * Through the layoutlib_create tool, selected methods of StaticLayout have been replaced
+ * Through the layoutlib_create tool, selected methods of Hyphenator have been replaced
* by calls to methods of the same name in this delegate class.
*/
public class Hyphenator_Delegate {
@@ -39,7 +39,8 @@
return null;
}
- /*package*/ static long loadHyphenator(ByteBuffer buf, int offset) {
+ /*package*/ @SuppressWarnings("UnusedParameters") // TODO implement this.
+ static long loadHyphenator(ByteBuffer buffer, int offset) {
return sDelegateManager.addNewDelegate(new Hyphenator_Delegate());
}
}
diff --git a/tools/layoutlib/bridge/src/android/util/PathParser_Delegate.java b/tools/layoutlib/bridge/src/android/util/PathParser_Delegate.java
new file mode 100644
index 0000000..d3af837
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/util/PathParser_Delegate.java
@@ -0,0 +1,757 @@
+/*
+ * 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 android.util;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.annotation.NonNull;
+import android.graphics.Path_Delegate;
+
+import java.awt.geom.Path2D;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Delegate that provides implementation for native methods in {@link android.util.PathParser}
+ * <p/>
+ * Through the layoutlib_create tool, selected methods of PathParser have been replaced by calls to
+ * methods of the same name in this delegate class.
+ *
+ * Most of the code has been taken from the implementation in
+ * {@code tools/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/PathParser.java}
+ * revision be6fe89a3b686db5a75e7e692a148699973957f3
+ */
+public class PathParser_Delegate {
+
+ private static final Logger LOGGER = Logger.getLogger("PathParser");
+
+ // ---- Builder delegate manager ----
+ private static final DelegateManager<PathParser_Delegate> sManager =
+ new DelegateManager<PathParser_Delegate>(PathParser_Delegate.class);
+
+ // ---- delegate data ----
+ @NonNull
+ private PathDataNode[] mPathDataNodes;
+
+ private PathParser_Delegate(@NonNull PathDataNode[] nodes) {
+ mPathDataNodes = nodes;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nParseStringForPath(long pathPtr, @NonNull String pathString, int
+ stringLength) {
+ Path_Delegate path_delegate = Path_Delegate.getDelegate(pathPtr);
+ if (path_delegate == null) {
+ return false;
+ }
+ assert pathString.length() == stringLength;
+ PathDataNode.nodesToPath(createNodesFromPathData(pathString), path_delegate.getJavaShape());
+ return true;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nCreatePathFromPathData(long outPathPtr, long pathData) {
+ Path_Delegate path_delegate = Path_Delegate.getDelegate(outPathPtr);
+ PathParser_Delegate source = sManager.getDelegate(outPathPtr);
+ if (source == null || path_delegate == null) {
+ return;
+ }
+ PathDataNode.nodesToPath(source.mPathDataNodes, path_delegate.getJavaShape());
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long nCreateEmptyPathData() {
+ PathParser_Delegate newDelegate = new PathParser_Delegate(new PathDataNode[0]);
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long nCreatePathData(long nativePtr) {
+ PathParser_Delegate source = sManager.getDelegate(nativePtr);
+ if (source == null) {
+ return 0;
+ }
+ PathParser_Delegate dest = new PathParser_Delegate(deepCopyNodes(source.mPathDataNodes));
+ return sManager.addNewDelegate(dest);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long nCreatePathDataFromString(@NonNull String pathString,
+ int stringLength) {
+ assert pathString.length() == stringLength : "Inconsistent path string length.";
+ PathDataNode[] nodes = createNodesFromPathData(pathString);
+ PathParser_Delegate delegate = new PathParser_Delegate(nodes);
+ return sManager.addNewDelegate(delegate);
+
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nInterpolatePathData(long outDataPtr, long fromDataPtr,
+ long toDataPtr, float fraction) {
+ PathParser_Delegate out = sManager.getDelegate(outDataPtr);
+ PathParser_Delegate from = sManager.getDelegate(fromDataPtr);
+ PathParser_Delegate to = sManager.getDelegate(toDataPtr);
+ if (out == null || from == null || to == null) {
+ return false;
+ }
+ int length = from.mPathDataNodes.length;
+ if (length != to.mPathDataNodes.length) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ "Cannot interpolate path data with different lengths (from " + length + " to " +
+ to.mPathDataNodes.length + ").", null);
+ return false;
+ }
+ if (out.mPathDataNodes.length != length) {
+ out.mPathDataNodes = new PathDataNode[length];
+ }
+ for (int i = 0; i < length; i++) {
+ out.mPathDataNodes[i].interpolatePathDataNode(from.mPathDataNodes[i],
+ to.mPathDataNodes[i], fraction);
+ }
+ return true;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nFinalize(long nativePtr) {
+ sManager.removeJavaReferenceFor(nativePtr);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nCanMorph(long fromDataPtr, long toDataPtr) {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, "morphing path data isn't " +
+ "supported", null, null);
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nSetPathData(long outDataPtr, long fromDataPtr) {
+ PathParser_Delegate out = sManager.getDelegate(outDataPtr);
+ PathParser_Delegate from = sManager.getDelegate(fromDataPtr);
+ if (from == null || out == null) {
+ return;
+ }
+ out.mPathDataNodes = deepCopyNodes(from.mPathDataNodes);
+ }
+
+ /**
+ * @param pathData The string representing a path, the same as "d" string in svg file.
+ *
+ * @return an array of the PathDataNode.
+ */
+ @NonNull
+ private static PathDataNode[] createNodesFromPathData(@NonNull String pathData) {
+ int start = 0;
+ int end = 1;
+
+ ArrayList<PathDataNode> list = new ArrayList<PathDataNode>();
+ while (end < pathData.length()) {
+ end = nextStart(pathData, end);
+ String s = pathData.substring(start, end).trim();
+ if (s.length() > 0) {
+ float[] val = getFloats(s);
+ addNode(list, s.charAt(0), val);
+ }
+
+ start = end;
+ end++;
+ }
+ if ((end - start) == 1 && start < pathData.length()) {
+ addNode(list, pathData.charAt(start), new float[0]);
+ }
+ return list.toArray(new PathDataNode[list.size()]);
+ }
+
+ /**
+ * @param source The array of PathDataNode to be duplicated.
+ *
+ * @return a deep copy of the <code>source</code>.
+ */
+ @NonNull
+ private static PathDataNode[] deepCopyNodes(@NonNull PathDataNode[] source) {
+ PathDataNode[] copy = new PathDataNode[source.length];
+ for (int i = 0; i < source.length; i++) {
+ copy[i] = new PathDataNode(source[i]);
+ }
+ return copy;
+ }
+
+ private static int nextStart(@NonNull String s, int end) {
+ char c;
+
+ while (end < s.length()) {
+ c = s.charAt(end);
+ // Note that 'e' or 'E' are not valid path commands, but could be
+ // used for floating point numbers' scientific notation.
+ // Therefore, when searching for next command, we should ignore 'e'
+ // and 'E'.
+ if ((((c - 'A') * (c - 'Z') <= 0) || ((c - 'a') * (c - 'z') <= 0))
+ && c != 'e' && c != 'E') {
+ return end;
+ }
+ end++;
+ }
+ return end;
+ }
+
+ /**
+ * Calculate the position of the next comma or space or negative sign
+ *
+ * @param s the string to search
+ * @param start the position to start searching
+ * @param result the result of the extraction, including the position of the the starting
+ * position of next number, whether it is ending with a '-'.
+ */
+ private static void extract(@NonNull String s, int start, @NonNull ExtractFloatResult result) {
+ // Now looking for ' ', ',', '.' or '-' from the start.
+ int currentIndex = start;
+ boolean foundSeparator = false;
+ result.mEndWithNegOrDot = false;
+ boolean secondDot = false;
+ boolean isExponential = false;
+ for (; currentIndex < s.length(); currentIndex++) {
+ boolean isPrevExponential = isExponential;
+ isExponential = false;
+ char currentChar = s.charAt(currentIndex);
+ switch (currentChar) {
+ case ' ':
+ case ',':
+ foundSeparator = true;
+ break;
+ case '-':
+ // The negative sign following a 'e' or 'E' is not a separator.
+ if (currentIndex != start && !isPrevExponential) {
+ foundSeparator = true;
+ result.mEndWithNegOrDot = true;
+ }
+ break;
+ case '.':
+ if (!secondDot) {
+ secondDot = true;
+ } else {
+ // This is the second dot, and it is considered as a separator.
+ foundSeparator = true;
+ result.mEndWithNegOrDot = true;
+ }
+ break;
+ case 'e':
+ case 'E':
+ isExponential = true;
+ break;
+ }
+ if (foundSeparator) {
+ break;
+ }
+ }
+ // When there is nothing found, then we put the end position to the end
+ // of the string.
+ result.mEndPosition = currentIndex;
+ }
+
+ /**
+ * Parse the floats in the string. This is an optimized version of
+ * parseFloat(s.split(",|\\s"));
+ *
+ * @param s the string containing a command and list of floats
+ *
+ * @return array of floats
+ */
+ @NonNull
+ private static float[] getFloats(@NonNull String s) {
+ if (s.charAt(0) == 'z' || s.charAt(0) == 'Z') {
+ return new float[0];
+ }
+ try {
+ float[] results = new float[s.length()];
+ int count = 0;
+ int startPosition = 1;
+ int endPosition;
+
+ ExtractFloatResult result = new ExtractFloatResult();
+ int totalLength = s.length();
+
+ // The startPosition should always be the first character of the
+ // current number, and endPosition is the character after the current
+ // number.
+ while (startPosition < totalLength) {
+ extract(s, startPosition, result);
+ endPosition = result.mEndPosition;
+
+ if (startPosition < endPosition) {
+ results[count++] = Float.parseFloat(
+ s.substring(startPosition, endPosition));
+ }
+
+ if (result.mEndWithNegOrDot) {
+ // Keep the '-' or '.' sign with next number.
+ startPosition = endPosition;
+ } else {
+ startPosition = endPosition + 1;
+ }
+ }
+ return Arrays.copyOf(results, count);
+ } catch (NumberFormatException e) {
+ throw new RuntimeException("error in parsing \"" + s + "\"", e);
+ }
+ }
+
+
+ private static void addNode(@NonNull ArrayList<PathDataNode> list, char cmd,
+ @NonNull float[] val) {
+ list.add(new PathDataNode(cmd, val));
+ }
+
+ private static class ExtractFloatResult {
+ // We need to return the position of the next separator and whether the
+ // next float starts with a '-' or a '.'.
+ private int mEndPosition;
+ private boolean mEndWithNegOrDot;
+ }
+
+ /**
+ * Each PathDataNode represents one command in the "d" attribute of the svg file. An array of
+ * PathDataNode can represent the whole "d" attribute.
+ */
+ private static class PathDataNode {
+ private char mType;
+ @NonNull
+ private float[] mParams;
+
+ private PathDataNode(char type, @NonNull float[] params) {
+ mType = type;
+ mParams = params;
+ }
+
+ public char getType() {
+ return mType;
+ }
+
+ @NonNull
+ public float[] getParams() {
+ return mParams;
+ }
+
+ private PathDataNode(@NonNull PathDataNode n) {
+ mType = n.mType;
+ mParams = Arrays.copyOf(n.mParams, n.mParams.length);
+ }
+
+ /**
+ * Convert an array of PathDataNode to Path.
+ *
+ * @param node The source array of PathDataNode.
+ * @param path The target Path object.
+ */
+ private static void nodesToPath(@NonNull PathDataNode[] node, @NonNull Path2D path) {
+ float[] current = new float[6];
+ char previousCommand = 'm';
+ //noinspection ForLoopReplaceableByForEach
+ for (int i = 0; i < node.length; i++) {
+ addCommand(path, current, previousCommand, node[i].mType, node[i].mParams);
+ previousCommand = node[i].mType;
+ }
+ }
+
+ /**
+ * The current PathDataNode will be interpolated between the <code>nodeFrom</code> and
+ * <code>nodeTo</code> according to the <code>fraction</code>.
+ *
+ * @param nodeFrom The start value as a PathDataNode.
+ * @param nodeTo The end value as a PathDataNode
+ * @param fraction The fraction to interpolate.
+ */
+ private void interpolatePathDataNode(@NonNull PathDataNode nodeFrom,
+ @NonNull PathDataNode nodeTo, float fraction) {
+ for (int i = 0; i < nodeFrom.mParams.length; i++) {
+ mParams[i] = nodeFrom.mParams[i] * (1 - fraction)
+ + nodeTo.mParams[i] * fraction;
+ }
+ }
+
+ @SuppressWarnings("PointlessArithmeticExpression")
+ private static void addCommand(@NonNull Path2D path, float[] current, char cmd,
+ char lastCmd, @NonNull float[] val) {
+
+ int incr = 2;
+
+ float cx = current[0];
+ float cy = current[1];
+ float cpx = current[2];
+ float cpy = current[3];
+ float loopX = current[4];
+ float loopY = current[5];
+
+ switch (cmd) {
+ case 'z':
+ case 'Z':
+ path.closePath();
+ cx = loopX;
+ cy = loopY;
+ case 'm':
+ case 'M':
+ case 'l':
+ case 'L':
+ case 't':
+ case 'T':
+ incr = 2;
+ break;
+ case 'h':
+ case 'H':
+ case 'v':
+ case 'V':
+ incr = 1;
+ break;
+ case 'c':
+ case 'C':
+ incr = 6;
+ break;
+ case 's':
+ case 'S':
+ case 'q':
+ case 'Q':
+ incr = 4;
+ break;
+ case 'a':
+ case 'A':
+ incr = 7;
+ }
+
+ for (int k = 0; k < val.length; k += incr) {
+ boolean reflectCtrl;
+ float tempReflectedX, tempReflectedY;
+
+ switch (cmd) {
+ case 'm':
+ cx += val[k + 0];
+ cy += val[k + 1];
+ if (k > 0) {
+ // According to the spec, if a moveto is followed by multiple
+ // pairs of coordinates, the subsequent pairs are treated as
+ // implicit lineto commands.
+ path.lineTo(cx, cy);
+ } else {
+ path.moveTo(cx, cy);
+ loopX = cx;
+ loopY = cy;
+ }
+ break;
+ case 'M':
+ cx = val[k + 0];
+ cy = val[k + 1];
+ if (k > 0) {
+ // According to the spec, if a moveto is followed by multiple
+ // pairs of coordinates, the subsequent pairs are treated as
+ // implicit lineto commands.
+ path.lineTo(cx, cy);
+ } else {
+ path.moveTo(cx, cy);
+ loopX = cx;
+ loopY = cy;
+ }
+ break;
+ case 'l':
+ cx += val[k + 0];
+ cy += val[k + 1];
+ path.lineTo(cx, cy);
+ break;
+ case 'L':
+ cx = val[k + 0];
+ cy = val[k + 1];
+ path.lineTo(cx, cy);
+ break;
+ case 'z':
+ case 'Z':
+ path.closePath();
+ cx = loopX;
+ cy = loopY;
+ break;
+ case 'h':
+ cx += val[k + 0];
+ path.lineTo(cx, cy);
+ break;
+ case 'H':
+ path.lineTo(val[k + 0], cy);
+ cx = val[k + 0];
+ break;
+ case 'v':
+ cy += val[k + 0];
+ path.lineTo(cx, cy);
+ break;
+ case 'V':
+ path.lineTo(cx, val[k + 0]);
+ cy = val[k + 0];
+ break;
+ case 'c':
+ path.curveTo(cx + val[k + 0], cy + val[k + 1], cx + val[k + 2],
+ cy + val[k + 3], cx + val[k + 4], cy + val[k + 5]);
+ cpx = cx + val[k + 2];
+ cpy = cy + val[k + 3];
+ cx += val[k + 4];
+ cy += val[k + 5];
+ break;
+ case 'C':
+ path.curveTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
+ val[k + 4], val[k + 5]);
+ cx = val[k + 4];
+ cy = val[k + 5];
+ cpx = val[k + 2];
+ cpy = val[k + 3];
+ break;
+ case 's':
+ reflectCtrl = (lastCmd == 'c' || lastCmd == 's' || lastCmd == 'C' ||
+ lastCmd == 'S');
+ path.curveTo(reflectCtrl ? 2 * cx - cpx : cx, reflectCtrl ? 2
+ * cy - cpy : cy, cx + val[k + 0], cy + val[k + 1], cx
+ + val[k + 2], cy + val[k + 3]);
+
+ cpx = cx + val[k + 0];
+ cpy = cy + val[k + 1];
+ cx += val[k + 2];
+ cy += val[k + 3];
+ break;
+ case 'S':
+ reflectCtrl = (lastCmd == 'c' || lastCmd == 's' || lastCmd == 'C' ||
+ lastCmd == 'S');
+ path.curveTo(reflectCtrl ? 2 * cx - cpx : cx, reflectCtrl ? 2
+ * cy - cpy : cy, val[k + 0], val[k + 1], val[k + 2],
+ val[k + 3]);
+ cpx = (val[k + 0]);
+ cpy = (val[k + 1]);
+ cx = val[k + 2];
+ cy = val[k + 3];
+ break;
+ case 'q':
+ path.quadTo(cx + val[k + 0], cy + val[k + 1], cx + val[k + 2],
+ cy + val[k + 3]);
+ cpx = cx + val[k + 0];
+ cpy = cy + val[k + 1];
+ // Note that we have to update cpx first, since cx will be updated here.
+ cx += val[k + 2];
+ cy += val[k + 3];
+ break;
+ case 'Q':
+ path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
+ cx = val[k + 2];
+ cy = val[k + 3];
+ cpx = val[k + 0];
+ cpy = val[k + 1];
+ break;
+ case 't':
+ reflectCtrl = (lastCmd == 'q' || lastCmd == 't' || lastCmd == 'Q' ||
+ lastCmd == 'T');
+ tempReflectedX = reflectCtrl ? 2 * cx - cpx : cx;
+ tempReflectedY = reflectCtrl ? 2 * cy - cpy : cy;
+ path.quadTo(tempReflectedX, tempReflectedY, cx + val[k + 0],
+ cy + val[k + 1]);
+ cpx = tempReflectedX;
+ cpy = tempReflectedY;
+ cx += val[k + 0];
+ cy += val[k + 1];
+ break;
+ case 'T':
+ reflectCtrl = (lastCmd == 'q' || lastCmd == 't' || lastCmd == 'Q' ||
+ lastCmd == 'T');
+ tempReflectedX = reflectCtrl ? 2 * cx - cpx : cx;
+ tempReflectedY = reflectCtrl ? 2 * cy - cpy : cy;
+ path.quadTo(tempReflectedX, tempReflectedY, val[k + 0], val[k + 1]);
+ cx = val[k + 0];
+ cy = val[k + 1];
+ cpx = tempReflectedX;
+ cpy = tempReflectedY;
+ break;
+ case 'a':
+ // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
+ drawArc(path, cx, cy, val[k + 5] + cx, val[k + 6] + cy,
+ val[k + 0], val[k + 1], val[k + 2], val[k + 3] != 0,
+ val[k + 4] != 0);
+ cx += val[k + 5];
+ cy += val[k + 6];
+ cpx = cx;
+ cpy = cy;
+
+ break;
+ case 'A':
+ drawArc(path, cx, cy, val[k + 5], val[k + 6], val[k + 0],
+ val[k + 1], val[k + 2], val[k + 3] != 0,
+ val[k + 4] != 0);
+ cx = val[k + 5];
+ cy = val[k + 6];
+ cpx = cx;
+ cpy = cy;
+ break;
+
+ }
+ lastCmd = cmd;
+ }
+ current[0] = cx;
+ current[1] = cy;
+ current[2] = cpx;
+ current[3] = cpy;
+ current[4] = loopX;
+ current[5] = loopY;
+
+ }
+
+ private static void drawArc(@NonNull Path2D p, float x0, float y0, float x1,
+ float y1, float a, float b, float theta, boolean isMoreThanHalf,
+ boolean isPositiveArc) {
+
+ LOGGER.log(Level.FINE, "(" + x0 + "," + y0 + ")-(" + x1 + "," + y1
+ + ") {" + a + " " + b + "}");
+ /* Convert rotation angle from degrees to radians */
+ double thetaD = theta * Math.PI / 180.0f;
+ /* Pre-compute rotation matrix entries */
+ double cosTheta = Math.cos(thetaD);
+ double sinTheta = Math.sin(thetaD);
+ /* Transform (x0, y0) and (x1, y1) into unit space */
+ /* using (inverse) rotation, followed by (inverse) scale */
+ double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
+ double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
+ double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
+ double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
+ LOGGER.log(Level.FINE, "unit space (" + x0p + "," + y0p + ")-(" + x1p
+ + "," + y1p + ")");
+ /* Compute differences and averages */
+ double dx = x0p - x1p;
+ double dy = y0p - y1p;
+ double xm = (x0p + x1p) / 2;
+ double ym = (y0p + y1p) / 2;
+ /* Solve for intersecting unit circles */
+ double dsq = dx * dx + dy * dy;
+ if (dsq == 0.0) {
+ LOGGER.log(Level.FINE, " Points are coincident");
+ return; /* Points are coincident */
+ }
+ double disc = 1.0 / dsq - 1.0 / 4.0;
+ if (disc < 0.0) {
+ LOGGER.log(Level.FINE, "Points are too far apart " + dsq);
+ float adjust = (float) (Math.sqrt(dsq) / 1.99999);
+ drawArc(p, x0, y0, x1, y1, a * adjust, b * adjust, theta,
+ isMoreThanHalf, isPositiveArc);
+ return; /* Points are too far apart */
+ }
+ double s = Math.sqrt(disc);
+ double sdx = s * dx;
+ double sdy = s * dy;
+ double cx;
+ double cy;
+ if (isMoreThanHalf == isPositiveArc) {
+ cx = xm - sdy;
+ cy = ym + sdx;
+ } else {
+ cx = xm + sdy;
+ cy = ym - sdx;
+ }
+
+ double eta0 = Math.atan2((y0p - cy), (x0p - cx));
+ LOGGER.log(Level.FINE, "eta0 = Math.atan2( " + (y0p - cy) + " , "
+ + (x0p - cx) + ") = " + Math.toDegrees(eta0));
+
+ double eta1 = Math.atan2((y1p - cy), (x1p - cx));
+ LOGGER.log(Level.FINE, "eta1 = Math.atan2( " + (y1p - cy) + " , "
+ + (x1p - cx) + ") = " + Math.toDegrees(eta1));
+ double sweep = (eta1 - eta0);
+ if (isPositiveArc != (sweep >= 0)) {
+ if (sweep > 0) {
+ sweep -= 2 * Math.PI;
+ } else {
+ sweep += 2 * Math.PI;
+ }
+ }
+
+ cx *= a;
+ cy *= b;
+ double tcx = cx;
+ cx = cx * cosTheta - cy * sinTheta;
+ cy = tcx * sinTheta + cy * cosTheta;
+ LOGGER.log(
+ Level.FINE,
+ "cx, cy, a, b, x0, y0, thetaD, eta0, sweep = " + cx + " , "
+ + cy + " , " + a + " , " + b + " , " + x0 + " , " + y0
+ + " , " + Math.toDegrees(thetaD) + " , "
+ + Math.toDegrees(eta0) + " , " + Math.toDegrees(sweep));
+
+ arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
+ }
+
+ /**
+ * Converts an arc to cubic Bezier segments and records them in p.
+ *
+ * @param p The target for the cubic Bezier segments
+ * @param cx The x coordinate center of the ellipse
+ * @param cy The y coordinate center of the ellipse
+ * @param a The radius of the ellipse in the horizontal direction
+ * @param b The radius of the ellipse in the vertical direction
+ * @param e1x E(eta1) x coordinate of the starting point of the arc
+ * @param e1y E(eta2) y coordinate of the starting point of the arc
+ * @param theta The angle that the ellipse bounding rectangle makes with the horizontal
+ * plane
+ * @param start The start angle of the arc on the ellipse
+ * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
+ */
+ private static void arcToBezier(@NonNull Path2D p, double cx, double cy, double a,
+ double b, double e1x, double e1y, double theta, double start,
+ double sweep) {
+ // Taken from equations at:
+ // http://spaceroots.org/documents/ellipse/node8.html
+ // and http://www.spaceroots.org/documents/ellipse/node22.html
+ // Maximum of 45 degrees per cubic Bezier segment
+ int numSegments = (int) Math.ceil(Math.abs(sweep * 4 / Math.PI));
+
+
+ double eta1 = start;
+ double cosTheta = Math.cos(theta);
+ double sinTheta = Math.sin(theta);
+ double cosEta1 = Math.cos(eta1);
+ double sinEta1 = Math.sin(eta1);
+ double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
+ double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
+
+ double anglePerSegment = sweep / numSegments;
+ for (int i = 0; i < numSegments; i++) {
+ double eta2 = eta1 + anglePerSegment;
+ double sinEta2 = Math.sin(eta2);
+ double cosEta2 = Math.cos(eta2);
+ double e2x = cx + (a * cosTheta * cosEta2)
+ - (b * sinTheta * sinEta2);
+ double e2y = cy + (a * sinTheta * cosEta2)
+ + (b * cosTheta * sinEta2);
+ double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
+ double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
+ double tanDiff2 = Math.tan((eta2 - eta1) / 2);
+ double alpha = Math.sin(eta2 - eta1)
+ * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
+ double q1x = e1x + alpha * ep1x;
+ double q1y = e1y + alpha * ep1y;
+ double q2x = e2x - alpha * ep2x;
+ double q2y = e2y - alpha * ep2y;
+
+ p.curveTo((float) q1x, (float) q1y, (float) q2x, (float) q2y,
+ (float) e2x, (float) e2y);
+ eta1 = eta2;
+ e1x = e2x;
+ e1y = e2y;
+ ep1x = ep2x;
+ ep1y = ep2y;
+ }
+ }
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/widget/TimePickerClockDelegate_Delegate.java b/tools/layoutlib/bridge/src/android/widget/TimePickerClockDelegate_Delegate.java
deleted file mode 100644
index 1bd9830..0000000
--- a/tools/layoutlib/bridge/src/android/widget/TimePickerClockDelegate_Delegate.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.widget;
-
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.view.KeyEvent;
-
-/**
- * Delegate used to provide new implementation of few methods in {@link TimePickerClockDelegate}.
- */
-public class TimePickerClockDelegate_Delegate {
-
- // Copied from TimePickerClockDelegate.
- private static final int AM = 0;
- private static final int PM = 1;
-
- @LayoutlibDelegate
- static int getAmOrPmKeyCode(TimePickerClockDelegate tpcd, int amOrPm) {
- // We don't care about locales here.
- if (amOrPm == AM) {
- return KeyEvent.KEYCODE_A;
- } else if (amOrPm == PM) {
- return KeyEvent.KEYCODE_P;
- } else {
- assert false : "amOrPm value in TimePickerSpinnerDelegate can only be 0 or 1";
- return -1;
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 4b89217..17ab2ff5 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -1382,6 +1382,12 @@
}
@Override
+ public File getDataDir() {
+ // pass
+ return null;
+ }
+
+ @Override
public File getFilesDir() {
// pass
return null;
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java
index 8c60bae..c0ca1d5 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java
@@ -36,9 +36,9 @@
/**
* A Helper class to do fake data binding in {@link AdapterView} objects.
*/
-@SuppressWarnings("deprecation")
public class AdapterHelper {
+ @SuppressWarnings("deprecation")
static Pair<View, Boolean> getView(AdapterItem item, AdapterItem parentItem, ViewGroup parent,
LayoutlibCallback callback, ResourceReference adapterRef, boolean skipCallbackParser) {
// we don't care about recycling here because we never scroll.
diff --git a/tools/layoutlib/bridge/src/libcore/util/NativeAllocationRegistry_Delegate.java b/tools/layoutlib/bridge/src/libcore/util/NativeAllocationRegistry_Delegate.java
new file mode 100644
index 0000000..6246ec1b8
--- /dev/null
+++ b/tools/layoutlib/bridge/src/libcore/util/NativeAllocationRegistry_Delegate.java
@@ -0,0 +1,64 @@
+/*
+ * 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 libcore.util;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate implementing the native methods of {@link NativeAllocationRegistry}
+ *
+ * Through the layoutlib_create tool, the original native methods of NativeAllocationRegistry have
+ * been replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original NativeAllocationRegistry class.
+ *
+ * @see DelegateManager
+ */
+public class NativeAllocationRegistry_Delegate {
+
+ // ---- delegate manager ----
+ private static final DelegateManager<NativeAllocationRegistry_Delegate> sManager =
+ new DelegateManager<>(NativeAllocationRegistry_Delegate.class);
+
+ private final FreeFunction mFinalizer;
+
+ private NativeAllocationRegistry_Delegate(FreeFunction finalizer) {
+ mFinalizer = finalizer;
+ }
+
+ /**
+ * The result of this method should be cached by the class and reused.
+ */
+ public static long createFinalizer(FreeFunction finalizer) {
+ return sManager.addNewDelegate(new NativeAllocationRegistry_Delegate(finalizer));
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void applyFreeFunction(long freeFunction, long nativePtr) {
+ NativeAllocationRegistry_Delegate delegate = sManager.getDelegate(freeFunction);
+ if (delegate != null) {
+ delegate.mFinalizer.free(nativePtr);
+ }
+ }
+
+ public interface FreeFunction {
+ void free(long nativePtr);
+ }
+}
diff --git a/tools/layoutlib/bridge/tests/Android.mk b/tools/layoutlib/bridge/tests/Android.mk
index 5eef24a..5c062d0 100644
--- a/tools/layoutlib/bridge/tests/Android.mk
+++ b/tools/layoutlib/bridge/tests/Android.mk
@@ -16,10 +16,11 @@
include $(CLEAR_VARS)
+LOCAL_JAVA_LANGUAGE_VERSION := 1.8
+
# Only compile source java files in this lib.
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_JAVA_RESOURCE_DIRS := res
-LOCAL_JAVACFLAGS := -source 6 -target 6
LOCAL_MODULE := layoutlib-tests
LOCAL_MODULE_TAGS := optional
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java
index 16911bd..8f9fa8a2 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java
@@ -75,8 +75,12 @@
}
}
else {
- BufferedImage goldenImage = ImageIO.read(is);
- assertImageSimilar(relativePath, goldenImage, thumbnail, MAX_PERCENT_DIFFERENCE);
+ try {
+ BufferedImage goldenImage = ImageIO.read(is);
+ assertImageSimilar(relativePath, goldenImage, thumbnail, MAX_PERCENT_DIFFERENCE);
+ } finally {
+ is.close();
+ }
}
}
diff --git a/tools/layoutlib/create/Android.mk b/tools/layoutlib/create/Android.mk
index c7f2c41..47377ae 100644
--- a/tools/layoutlib/create/Android.mk
+++ b/tools/layoutlib/create/Android.mk
@@ -16,6 +16,8 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
+LOCAL_JAVA_LANGUAGE_VERSION := 1.8
+
LOCAL_SRC_FILES := $(call all-java-files-under,src)
LOCAL_JAR_MANIFEST := manifest.txt
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java
index 758bd48..01c940a 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java
@@ -176,17 +176,6 @@
}
}
- /* Java 7 verifies the StackMapTable of a class if its version number is greater than 50.0.
- * However, the check is disabled if the class version number is 50.0 or less. Generation
- * of the StackMapTable requires a rewrite using the tree API of ASM. As a workaround,
- * we rewrite the version number of the class to be 50.0
- *
- * http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6693236
- */
- if (version > 50) {
- version = 50;
- }
-
super.visit(version, access, name, signature, superName, interfaces);
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index d106592..9e390f6 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -144,11 +144,8 @@
InjectMethodRunnable.class,
InjectMethodRunnables.class,
/* Java package classes */
- AutoCloseable.class,
- Objects.class,
IntegralToString.class,
UnsafeByteSequence.class,
- Charsets.class,
System_Delegate.class,
LinkedHashMap_Delegate.class,
};
@@ -169,6 +166,7 @@
"android.graphics.BitmapFactory#setDensityFromOptions",
"android.graphics.drawable.GradientDrawable#buildRing",
"android.graphics.Typeface#getSystemFontConfigLocation",
+ "android.graphics.Typeface#makeFamilyFromParsed",
"android.os.Handler#sendMessageAtTime",
"android.os.HandlerThread#run",
"android.preference.Preference#getView",
@@ -188,6 +186,7 @@
"android.view.WindowManagerGlobal#getWindowManagerService",
"android.view.inputmethod.InputMethodManager#getInstance",
"android.view.MenuInflater#registerMenu",
+ "android.view.RenderNode#getMatrix",
"android.view.RenderNode#nCreate",
"android.view.RenderNode#nDestroyRenderNode",
"android.view.RenderNode#nSetElevation",
@@ -200,7 +199,6 @@
"android.view.RenderNode#nGetTranslationZ",
"android.view.RenderNode#nSetRotation",
"android.view.RenderNode#nGetRotation",
- "android.view.RenderNode#getMatrix",
"android.view.RenderNode#nSetLeft",
"android.view.RenderNode#nSetTop",
"android.view.RenderNode#nSetRight",
@@ -216,7 +214,6 @@
"android.view.RenderNode#nGetScaleY",
"android.view.RenderNode#nIsPivotExplicitlySet",
"android.view.ViewGroup#drawChild",
- "android.widget.TimePickerClockDelegate#getAmOrPmKeyCode",
"com.android.internal.view.menu.MenuBuilder#createNewMenuItem",
"com.android.internal.util.XmlUtils#convertValueToInt",
"com.android.internal.textservice.ITextServicesManager$Stub#asInterface",
@@ -224,6 +221,7 @@
"libcore.io.MemoryMappedFile#mmapRO",
"libcore.io.MemoryMappedFile#close",
"libcore.io.MemoryMappedFile#bigEndianIterator",
+ "libcore.util.NativeAllocationRegistry#applyFreeFunction",
};
/**
@@ -274,6 +272,7 @@
"android.os.SystemProperties",
"android.text.AndroidBidi",
"android.text.StaticLayout",
+ "android.util.PathParser",
"android.view.Display",
"libcore.icu.ICU",
};
@@ -306,12 +305,13 @@
*/
private final static String[] JAVA_PKG_CLASSES =
new String[] {
- "java.lang.AutoCloseable", "com.android.tools.layoutlib.java.AutoCloseable",
- "java.util.Objects", "com.android.tools.layoutlib.java.Objects",
- "java.nio.charset.Charsets", "com.android.tools.layoutlib.java.Charsets",
+ "java.nio.charset.Charsets", "java.nio.charset.StandardCharsets",
"java.lang.IntegralToString", "com.android.tools.layoutlib.java.IntegralToString",
"java.lang.UnsafeByteSequence", "com.android.tools.layoutlib.java.UnsafeByteSequence",
- "java.nio.charset.StandardCharsets", "com.android.tools.layoutlib.java.Charsets",
+ // Use android.icu.text versions of DateFormat and SimpleDateFormat since the
+ // original ones do not match the Android implementation
+ "java.text.DateFormat", "android.icu.text.DateFormat",
+ "java.text.SimpleDateFormat", "android.icu.text.SimpleDateFormat"
};
private final static String[] EXCLUDED_CLASSES =
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java
index b5ab738..4ba7237 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java
@@ -103,6 +103,7 @@
mParentVisitor.visitInsn(Opcodes.ICONST_1);
mParentVisitor.visitInsn(Opcodes.IRETURN);
mParentVisitor.visitLabel(l1);
+ mParentVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
mParentVisitor.visitInsn(Opcodes.ICONST_0);
break;
case Type.CHAR:
diff --git a/tools/layoutlib/create/tests/Android.mk b/tools/layoutlib/create/tests/Android.mk
index dafb9c6..c59528e8 100644
--- a/tools/layoutlib/create/tests/Android.mk
+++ b/tools/layoutlib/create/tests/Android.mk
@@ -15,6 +15,8 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
+LOCAL_JAVA_LANGUAGE_VERSION := 1.8
+
# Only compile source java files in this lib.
LOCAL_SRC_FILES := $(call all-java-files-under, com)
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index 44be671..2373754 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -155,10 +155,6 @@
* Do not place scans in the chip's scan history buffer
*/
public static final int REPORT_EVENT_NO_BATCH = (1 << 2);
- /**
- * report full scan results and completion event to the context hub
- */
- public static final int REPORT_EVENT_CONTEXT_HUB = (1 << 3);
/**
* scan configuration parameters to be sent to {@link #startBackgroundScan}