Merge "Protect MotionEvent stream when a11y changes." into nyc-dev
diff --git a/api/current.txt b/api/current.txt
index a082a23..5d28d2c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4275,6 +4275,7 @@
public class DownloadManager {
method public long addCompletedDownload(java.lang.String, java.lang.String, boolean, java.lang.String, java.lang.String, long, boolean);
+ method public long addCompletedDownload(java.lang.String, java.lang.String, boolean, java.lang.String, java.lang.String, long, boolean, android.net.Uri, android.net.Uri);
method public long enqueue(android.app.DownloadManager.Request);
method public static java.lang.Long getMaxBytesOverMobile(android.content.Context);
method public java.lang.String getMimeTypeForDownloadedFile(long);
@@ -8533,7 +8534,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";
@@ -28574,7 +28574,6 @@
field public static java.lang.String DIRECTORY_DCIM;
field public static java.lang.String DIRECTORY_DOCUMENTS;
field public static java.lang.String DIRECTORY_DOWNLOADS;
- field public static java.lang.String DIRECTORY_HOME;
field public static java.lang.String DIRECTORY_MOVIES;
field public static java.lang.String DIRECTORY_MUSIC;
field public static java.lang.String DIRECTORY_NOTIFICATIONS;
@@ -29365,11 +29364,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 +31484,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";
@@ -32153,6 +32170,7 @@
field public static final java.lang.String ACTION_PRIVACY_SETTINGS = "android.settings.PRIVACY_SETTINGS";
field public static final java.lang.String ACTION_QUICK_LAUNCH_SETTINGS = "android.settings.QUICK_LAUNCH_SETTINGS";
field public static final java.lang.String ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
+ field public static final java.lang.String ACTION_SCREEN_READER_TUTORIAL = "android.settings.SCREEN_READER_TUTORIAL";
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";
diff --git a/api/system-current.txt b/api/system-current.txt
index e4437aa..a2943ff 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4407,6 +4407,7 @@
public class DownloadManager {
method public long addCompletedDownload(java.lang.String, java.lang.String, boolean, java.lang.String, java.lang.String, long, boolean);
+ method public long addCompletedDownload(java.lang.String, java.lang.String, boolean, java.lang.String, java.lang.String, long, boolean, android.net.Uri, android.net.Uri);
method public long enqueue(android.app.DownloadManager.Request);
method public static java.lang.Long getMaxBytesOverMobile(android.content.Context);
method public java.lang.String getMimeTypeForDownloadedFile(long);
@@ -8839,7 +8840,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";
@@ -24281,19 +24281,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);
@@ -26806,7 +26813,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
@@ -30858,7 +30864,6 @@
field public static java.lang.String DIRECTORY_DCIM;
field public static java.lang.String DIRECTORY_DOCUMENTS;
field public static java.lang.String DIRECTORY_DOWNLOADS;
- field public static java.lang.String DIRECTORY_HOME;
field public static java.lang.String DIRECTORY_MOVIES;
field public static java.lang.String DIRECTORY_MUSIC;
field public static java.lang.String DIRECTORY_NOTIFICATIONS;
@@ -31713,11 +31718,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 +33869,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";
@@ -34634,6 +34657,7 @@
field public static final java.lang.String ACTION_PRIVACY_SETTINGS = "android.settings.PRIVACY_SETTINGS";
field public static final java.lang.String ACTION_QUICK_LAUNCH_SETTINGS = "android.settings.QUICK_LAUNCH_SETTINGS";
field public static final java.lang.String ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
+ field public static final java.lang.String ACTION_SCREEN_READER_TUTORIAL = "android.settings.SCREEN_READER_TUTORIAL";
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";
@@ -34694,6 +34718,7 @@
field public static final deprecated java.lang.String INSTALL_NON_MARKET_APPS = "install_non_market_apps";
field public static final java.lang.String MODE_RINGER = "mode_ringer";
field public static final java.lang.String NETWORK_PREFERENCE = "network_preference";
+ field public static final java.lang.String OTA_DISABLE_AUTOMATIC_UPDATE = "ota_disable_automatic_update";
field public static final java.lang.String RADIO_BLUETOOTH = "bluetooth";
field public static final java.lang.String RADIO_CELL = "cell";
field public static final java.lang.String RADIO_NFC = "nfc";
diff --git a/api/test-current.txt b/api/test-current.txt
index de1496b..d42c18c 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -4275,6 +4275,7 @@
public class DownloadManager {
method public long addCompletedDownload(java.lang.String, java.lang.String, boolean, java.lang.String, java.lang.String, long, boolean);
+ method public long addCompletedDownload(java.lang.String, java.lang.String, boolean, java.lang.String, java.lang.String, long, boolean, android.net.Uri, android.net.Uri);
method public long enqueue(android.app.DownloadManager.Request);
method public static java.lang.Long getMaxBytesOverMobile(android.content.Context);
method public java.lang.String getMimeTypeForDownloadedFile(long);
@@ -8538,7 +8539,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";
@@ -28583,7 +28583,6 @@
field public static java.lang.String DIRECTORY_DCIM;
field public static java.lang.String DIRECTORY_DOCUMENTS;
field public static java.lang.String DIRECTORY_DOWNLOADS;
- field public static java.lang.String DIRECTORY_HOME;
field public static java.lang.String DIRECTORY_MOVIES;
field public static java.lang.String DIRECTORY_MUSIC;
field public static java.lang.String DIRECTORY_NOTIFICATIONS;
@@ -29375,11 +29374,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 +31497,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";
@@ -32166,6 +32183,7 @@
field public static final java.lang.String ACTION_PRIVACY_SETTINGS = "android.settings.PRIVACY_SETTINGS";
field public static final java.lang.String ACTION_QUICK_LAUNCH_SETTINGS = "android.settings.QUICK_LAUNCH_SETTINGS";
field public static final java.lang.String ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
+ field public static final java.lang.String ACTION_SCREEN_READER_TUTORIAL = "android.settings.SCREEN_READER_TUTORIAL";
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";
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/DownloadManager.java b/core/java/android/app/DownloadManager.java
index ed4bb28..1e5f007 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -1193,13 +1193,52 @@
boolean isMediaScannerScannable, String mimeType, String path, long length,
boolean showNotification) {
return addCompletedDownload(title, description, isMediaScannerScannable, mimeType, path,
- length, showNotification, false);
+ length, showNotification, false, null, null);
+ }
+
+ /**
+ * Adds a file to the downloads database system, so it could appear in Downloads App
+ * (and thus become eligible for management by the Downloads App).
+ * <p>
+ * It is helpful to make the file scannable by MediaScanner by setting the param
+ * isMediaScannerScannable to true. It makes the file visible in media managing
+ * applications such as Gallery App, which could be a useful purpose of using this API.
+ *
+ * @param title the title that would appear for this file in Downloads App.
+ * @param description the description that would appear for this file in Downloads App.
+ * @param isMediaScannerScannable true if the file is to be scanned by MediaScanner. Files
+ * scanned by MediaScanner appear in the applications used to view media (for example,
+ * Gallery app).
+ * @param mimeType mimetype of the file.
+ * @param path absolute pathname to the file. The file should be world-readable, so that it can
+ * be managed by the Downloads App and any other app that is used to read it (for example,
+ * Gallery app to display the file, if the file contents represent a video/image).
+ * @param length length of the downloaded file
+ * @param showNotification true if a notification is to be sent, false otherwise
+ * @param uri the original HTTP URI of the download
+ * @param referer the HTTP Referer for the download
+ * @return an ID for the download entry added to the downloads app, unique across the system
+ * This ID is used to make future calls related to this download.
+ */
+ public long addCompletedDownload(String title, String description,
+ boolean isMediaScannerScannable, String mimeType, String path, long length,
+ boolean showNotification, Uri uri, Uri referer) {
+ return addCompletedDownload(title, description, isMediaScannerScannable, mimeType, path,
+ length, showNotification, false, uri, referer);
}
/** {@hide} */
public long addCompletedDownload(String title, String description,
boolean isMediaScannerScannable, String mimeType, String path, long length,
boolean showNotification, boolean allowWrite) {
+ return addCompletedDownload(title, description, isMediaScannerScannable, mimeType, path,
+ length, showNotification, allowWrite, null, null);
+ }
+
+ /** {@hide} */
+ public long addCompletedDownload(String title, String description,
+ boolean isMediaScannerScannable, String mimeType, String path, long length,
+ boolean showNotification, boolean allowWrite, Uri uri, Uri referer) {
// make sure the input args are non-null/non-zero
validateArgumentIsNonEmpty("title", title);
validateArgumentIsNonEmpty("description", description);
@@ -1210,10 +1249,18 @@
}
// if there is already an entry with the given path name in downloads.db, return its id
- Request request = new Request(NON_DOWNLOADMANAGER_DOWNLOAD)
- .setTitle(title)
+ Request request;
+ if (uri != null) {
+ request = new Request(uri);
+ } else {
+ request = new Request(NON_DOWNLOADMANAGER_DOWNLOAD);
+ }
+ request.setTitle(title)
.setDescription(description)
.setMimeType(mimeType);
+ if (referer != null) {
+ request.addRequestHeader("Referer", referer.toString());
+ }
ContentValues values = request.toContentValues(null);
values.put(Downloads.Impl.COLUMN_DESTINATION,
Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD);
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/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index bed91ec..cd8f126 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -376,7 +376,13 @@
* AppWidget provider. Will animate into these new views as needed
*/
public void updateAppWidget(RemoteViews remoteViews) {
+ applyRemoteViews(remoteViews);
+ }
+ /**
+ * @hide
+ */
+ protected void applyRemoteViews(RemoteViews remoteViews) {
if (LOGD) Log.d(TAG, "updateAppWidget called mOld=" + mOld);
boolean recycled = false;
@@ -573,8 +579,9 @@
/**
* Build a {@link Context} cloned into another package name, usually for the
* purposes of reading remote resources.
+ * @hide
*/
- private Context getRemoteContext() {
+ protected Context getRemoteContext() {
try {
// Return if cloned successfully, otherwise default
return mContext.createApplicationContext(
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/os/Environment.java b/core/java/android/os/Environment.java
index 59bf2938..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>
@@ -476,11 +476,6 @@
public static String DIRECTORY_DOCUMENTS = "Documents";
/**
- * Standard directory in which user managed files are stored.
- */
- public static String DIRECTORY_HOME = "Home";
-
- /**
* List of standard storage directories.
* <p>
* Each of its values have its own constant:
@@ -495,7 +490,6 @@
* <li>{@link #DIRECTORY_DOWNLOADS}
* <li>{@link #DIRECTORY_DCIM}
* <li>{@link #DIRECTORY_DOCUMENTS}
- * <li>{@link #DIRECTORY_HOME}
* </ul>
* @hide
*/
@@ -509,8 +503,7 @@
DIRECTORY_MOVIES,
DIRECTORY_DOWNLOADS,
DIRECTORY_DCIM,
- DIRECTORY_DOCUMENTS,
- DIRECTORY_HOME
+ DIRECTORY_DOCUMENTS
};
/**
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 3169bf4..5ab2b00 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -201,6 +201,21 @@
"android.settings.ACCESSIBILITY_SETTINGS";
/**
+ * Activity Action: Launch the screen reader tutorial.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SCREEN_READER_TUTORIAL =
+ "android.settings.SCREEN_READER_TUTORIAL";
+
+
+ /**
* Activity Action: Show settings to control access to usage information.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
@@ -6716,6 +6731,16 @@
public static final String STORAGE_BENCHMARK_INTERVAL = "storage_benchmark_interval";
/**
+ * Whether to disable the automatic scheduling of system updates.
+ * 1 = system updates won't be automatically scheduled (will always
+ * present notification instead).
+ * 0 = system updates will be automatically scheduled. (default)
+ * @hide
+ */
+ @SystemApi
+ public static final String OTA_DISABLE_AUTOMATIC_UPDATE = "ota_disable_automatic_update";
+
+ /**
* Whether the package manager should send package verification broadcasts for verifiers to
* review apps prior to installation.
* 1 = request apps to be verified prior to installation, if a verifier exists.
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/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 96853e0..6dc5ccc 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.
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index 5953a98..8278c5a 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -18,11 +18,13 @@
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import android.Manifest;
+import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.Intent;
@@ -34,6 +36,9 @@
import android.os.RemoteException;
import android.util.Log;
import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.MeasureSpec;
@@ -74,7 +79,7 @@
private RemoteViewsAdapterServiceConnection mServiceConnection;
private WeakReference<RemoteAdapterConnectionCallback> mCallback;
private OnClickHandler mRemoteViewsOnClickHandler;
- private FixedSizeRemoteViewsCache mCache;
+ private final FixedSizeRemoteViewsCache mCache;
private int mVisibleWindowLowerBound;
private int mVisibleWindowUpperBound;
@@ -92,13 +97,10 @@
// We cache the FixedSizeRemoteViewsCaches across orientation. These are the related data
// structures;
- private static final HashMap<RemoteViewsCacheKey,
- FixedSizeRemoteViewsCache> sCachedRemoteViewsCaches
- = new HashMap<RemoteViewsCacheKey,
- FixedSizeRemoteViewsCache>();
+ private static final HashMap<RemoteViewsCacheKey, FixedSizeRemoteViewsCache>
+ sCachedRemoteViewsCaches = new HashMap<>();
private static final HashMap<RemoteViewsCacheKey, Runnable>
- sRemoteViewsCacheRemoveRunnables
- = new HashMap<RemoteViewsCacheKey, Runnable>();
+ sRemoteViewsCacheRemoveRunnables = new HashMap<>();
private static HandlerThread sCacheRemovalThread;
private static Handler sCacheRemovalQueue;
@@ -286,9 +288,12 @@
* A FrameLayout which contains a loading view, and manages the re/applying of RemoteViews when
* they are loaded.
*/
- private static class RemoteViewsFrameLayout extends FrameLayout {
- public RemoteViewsFrameLayout(Context context) {
+ private static class RemoteViewsFrameLayout extends AppWidgetHostView {
+ private final FixedSizeRemoteViewsCache mCache;
+
+ public RemoteViewsFrameLayout(Context context, FixedSizeRemoteViewsCache cache) {
super(context);
+ mCache = cache;
}
/**
@@ -297,13 +302,24 @@
* successfully.
*/
public void onRemoteViewsLoaded(RemoteViews view, OnClickHandler handler) {
- try {
- // Remove all the children of this layout first
- removeAllViews();
- addView(view.apply(getContext(), this, handler));
- } catch (Exception e) {
- Log.e(TAG, "Failed to apply RemoteViews.");
- }
+ setOnClickHandler(handler);
+ applyRemoteViews(view);
+ }
+
+ @Override
+ protected View getDefaultView() {
+ return mCache.getMetaData().createDefaultLoadingView(this);
+ }
+
+ @Override
+ protected Context getRemoteContext() {
+ return null;
+ }
+
+ @Override
+ protected View getErrorView() {
+ // Use the default loading view as the error view.
+ return getDefaultView();
}
}
@@ -312,29 +328,21 @@
* adapter that have not yet had their RemoteViews loaded.
*/
private class RemoteViewsFrameLayoutRefSet {
- private HashMap<Integer, LinkedList<RemoteViewsFrameLayout>> mReferences;
- private HashMap<RemoteViewsFrameLayout, LinkedList<RemoteViewsFrameLayout>>
- mViewToLinkedList;
-
- public RemoteViewsFrameLayoutRefSet() {
- mReferences = new HashMap<Integer, LinkedList<RemoteViewsFrameLayout>>();
- mViewToLinkedList =
- new HashMap<RemoteViewsFrameLayout, LinkedList<RemoteViewsFrameLayout>>();
- }
+ private final SparseArray<LinkedList<RemoteViewsFrameLayout>> mReferences =
+ new SparseArray<>();
+ private final HashMap<RemoteViewsFrameLayout, LinkedList<RemoteViewsFrameLayout>>
+ mViewToLinkedList = new HashMap<>();
/**
* Adds a new reference to a RemoteViewsFrameLayout returned by the adapter.
*/
public void add(int position, RemoteViewsFrameLayout layout) {
- final Integer pos = position;
- LinkedList<RemoteViewsFrameLayout> refs;
+ LinkedList<RemoteViewsFrameLayout> refs = mReferences.get(position);
// Create the list if necessary
- if (mReferences.containsKey(pos)) {
- refs = mReferences.get(pos);
- } else {
+ if (refs == null) {
refs = new LinkedList<RemoteViewsFrameLayout>();
- mReferences.put(pos, refs);
+ mReferences.put(position, refs);
}
mViewToLinkedList.put(layout, refs);
@@ -349,10 +357,9 @@
public void notifyOnRemoteViewsLoaded(int position, RemoteViews view) {
if (view == null) return;
- final Integer pos = position;
- if (mReferences.containsKey(pos)) {
+ final LinkedList<RemoteViewsFrameLayout> refs = mReferences.get(position);
+ if (refs != null) {
// Notify all the references for that position of the newly loaded RemoteViews
- final LinkedList<RemoteViewsFrameLayout> refs = mReferences.get(pos);
for (final RemoteViewsFrameLayout ref : refs) {
ref.onRemoteViewsLoaded(view, mRemoteViewsOnClickHandler);
if (mViewToLinkedList.containsKey(ref)) {
@@ -361,7 +368,7 @@
}
refs.clear();
// Remove this set from the original mapping
- mReferences.remove(pos);
+ mReferences.remove(position);
}
}
@@ -402,7 +409,7 @@
int mFirstViewHeight;
// A mapping from type id to a set of unique type ids
- private final HashMap<Integer, Integer> mTypeIdIndexMap = new HashMap<Integer, Integer>();
+ private final SparseIntArray mTypeIdIndexMap = new SparseIntArray();
public RemoteViewsMetaData() {
reset();
@@ -438,82 +445,47 @@
}
public int getMappedViewType(int typeId) {
- if (mTypeIdIndexMap.containsKey(typeId)) {
- return mTypeIdIndexMap.get(typeId);
- } else {
+ int mappedTypeId = mTypeIdIndexMap.get(typeId, -1);
+ if (mappedTypeId == -1) {
// We +1 because the loading view always has view type id of 0
- int incrementalTypeId = mTypeIdIndexMap.size() + 1;
- mTypeIdIndexMap.put(typeId, incrementalTypeId);
- return incrementalTypeId;
+ mappedTypeId = mTypeIdIndexMap.size() + 1;
+ mTypeIdIndexMap.put(typeId, mappedTypeId);
}
+ return mappedTypeId;
}
public boolean isViewTypeInRange(int typeId) {
int mappedType = getMappedViewType(typeId);
- if (mappedType >= viewTypeCount) {
- return false;
- } else {
- return true;
- }
+ return (mappedType < viewTypeCount);
}
- private RemoteViewsFrameLayout createLoadingView(int position, View convertView,
- ViewGroup parent, Object lock, LayoutInflater layoutInflater, OnClickHandler
- handler) {
- // Create and return a new FrameLayout, and setup the references for this position
+ /**
+ * Creates a default loading view. Uses the size of the first row as a guide for the
+ * size of the loading view.
+ */
+ private synchronized View createDefaultLoadingView(ViewGroup parent) {
final Context context = parent.getContext();
- RemoteViewsFrameLayout layout = new RemoteViewsFrameLayout(context);
-
- // Create a new loading view
- synchronized (lock) {
- boolean customLoadingViewAvailable = false;
-
- if (mUserLoadingView != null) {
- // Try to inflate user-specified loading view
- try {
- View loadingView = mUserLoadingView.apply(parent.getContext(), parent,
- handler);
- loadingView.setTagInternal(com.android.internal.R.id.rowTypeId,
- new Integer(0));
- layout.addView(loadingView);
- customLoadingViewAvailable = true;
- } catch (Exception e) {
- Log.w(TAG, "Error inflating custom loading view, using default loading" +
- "view instead", e);
- }
+ if (mFirstViewHeight < 0) {
+ try {
+ View firstView = mFirstView.apply(parent.getContext(), parent);
+ firstView.measure(
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+ mFirstViewHeight = firstView.getMeasuredHeight();
+ } catch (Exception e) {
+ float density = context.getResources().getDisplayMetrics().density;
+ mFirstViewHeight = Math.round(sDefaultLoadingViewHeight * density);
+ Log.w(TAG, "Error inflating first RemoteViews" + e);
}
- if (!customLoadingViewAvailable) {
- // A default loading view
- // Use the size of the first row as a guide for the size of the loading view
- if (mFirstViewHeight < 0) {
- try {
- View firstView = mFirstView.apply(parent.getContext(), parent, handler);
- firstView.measure(
- MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
- MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
- mFirstViewHeight = firstView.getMeasuredHeight();
- mFirstView = null;
- } catch (Exception e) {
- float density = context.getResources().getDisplayMetrics().density;
- mFirstViewHeight = (int)
- Math.round(sDefaultLoadingViewHeight * density);
- mFirstView = null;
- Log.w(TAG, "Error inflating first RemoteViews" + e);
- }
- }
-
- // Compose the loading view text
- TextView loadingTextView = (TextView) layoutInflater.inflate(
- com.android.internal.R.layout.remote_views_adapter_default_loading_view,
- layout, false);
- loadingTextView.setHeight(mFirstViewHeight);
- loadingTextView.setTag(new Integer(0));
-
- layout.addView(loadingTextView);
- }
+ mFirstView = null;
}
- return layout;
+ // Compose the loading view text
+ TextView loadingTextView = (TextView) LayoutInflater.from(context).inflate(
+ com.android.internal.R.layout.remote_views_adapter_default_loading_view,
+ parent, false);
+ loadingTextView.setHeight(mFirstViewHeight);
+ return loadingTextView;
}
}
@@ -548,8 +520,8 @@
// The meta data objects are made final so that they can be locked on independently
// of the FixedSizeRemoteViewsCache. If we ever lock on both meta data objects, it is in
// the order mTemporaryMetaData followed by mMetaData.
- private final RemoteViewsMetaData mMetaData;
- private final RemoteViewsMetaData mTemporaryMetaData;
+ private final RemoteViewsMetaData mMetaData = new RemoteViewsMetaData();
+ private final RemoteViewsMetaData mTemporaryMetaData = new RemoteViewsMetaData();
// The cache/mapping of position to RemoteViewsMetaData. This set is guaranteed to be
// greater than or equal to the set of RemoteViews.
@@ -558,22 +530,20 @@
// the heavy RemoteViews around. The RemoteViews cache is trimmed to fixed constraints wrt.
// memory and size, but this metadata cache will retain information until the data at the
// position is guaranteed as not being necessary any more (usually on notifyDataSetChanged).
- private HashMap<Integer, RemoteViewsIndexMetaData> mIndexMetaData;
+ private final SparseArray<RemoteViewsIndexMetaData> mIndexMetaData = new SparseArray<>();
// The cache of actual RemoteViews, which may be pruned if the cache gets too large, or uses
// too much memory.
- private HashMap<Integer, RemoteViews> mIndexRemoteViews;
+ private final SparseArray<RemoteViews> mIndexRemoteViews = new SparseArray<>();
- // The set of indices that have been explicitly requested by the collection view
- private HashSet<Integer> mRequestedIndices;
+ // An array of indices to load, Indices which are explicitely requested are set to true,
+ // and those determined by the preloading algorithm to prefetch are set to false.
+ private final SparseBooleanArray mIndicesToLoad = new SparseBooleanArray();
// We keep a reference of the last requested index to determine which item to prune the
// farthest items from when we hit the memory limit
private int mLastRequestedIndex;
- // The set of indices to load, including those explicitly requested, as well as those
- // determined by the preloading algorithm to be prefetched
- private HashSet<Integer> mLoadIndices;
// The lower and upper bounds of the preloaded range
private int mPreloadLowerBound;
@@ -584,8 +554,8 @@
// The maxCountSlack is used to determine if a new position in the cache to be loaded is
// sufficiently ouside the old set, prompting a shifting of the "window" of items to be
// preloaded.
- private int mMaxCount;
- private int mMaxCountSlack;
+ private final int mMaxCount;
+ private final int mMaxCountSlack;
private static final float sMaxCountSlackPercent = 0.75f;
private static final int sMaxMemoryLimitInBytes = 2 * 1024 * 1024;
@@ -594,17 +564,10 @@
mMaxCountSlack = Math.round(sMaxCountSlackPercent * (mMaxCount / 2));
mPreloadLowerBound = 0;
mPreloadUpperBound = -1;
- mMetaData = new RemoteViewsMetaData();
- mTemporaryMetaData = new RemoteViewsMetaData();
- mIndexMetaData = new HashMap<Integer, RemoteViewsIndexMetaData>();
- mIndexRemoteViews = new HashMap<Integer, RemoteViews>();
- mRequestedIndices = new HashSet<Integer>();
mLastRequestedIndex = -1;
- mLoadIndices = new HashSet<Integer>();
}
- public void insert(int position, RemoteViews v, long itemId,
- ArrayList<Integer> visibleWindow) {
+ public void insert(int position, RemoteViews v, long itemId, int[] visibleWindow) {
// Trim the cache if we go beyond the count
if (mIndexRemoteViews.size() >= mMaxCount) {
mIndexRemoteViews.remove(getFarthestPositionFrom(position, visibleWindow));
@@ -630,8 +593,8 @@
}
// Update the metadata cache
- if (mIndexMetaData.containsKey(position)) {
- final RemoteViewsIndexMetaData metaData = mIndexMetaData.get(position);
+ final RemoteViewsIndexMetaData metaData = mIndexMetaData.get(position);
+ if (metaData != null) {
metaData.set(v, itemId);
} else {
mIndexMetaData.put(position, new RemoteViewsIndexMetaData(v, itemId));
@@ -646,16 +609,10 @@
return mTemporaryMetaData;
}
public RemoteViews getRemoteViewsAt(int position) {
- if (mIndexRemoteViews.containsKey(position)) {
- return mIndexRemoteViews.get(position);
- }
- return null;
+ return mIndexRemoteViews.get(position);
}
public RemoteViewsIndexMetaData getMetaDataAt(int position) {
- if (mIndexMetaData.containsKey(position)) {
- return mIndexMetaData.get(position);
- }
- return null;
+ return mIndexMetaData.get(position);
}
public void commitTemporaryMetaData() {
@@ -669,8 +626,8 @@
private int getRemoteViewsBitmapMemoryUsage() {
// Calculate the memory usage of all the RemoteViews bitmaps being cached
int mem = 0;
- for (Integer i : mIndexRemoteViews.keySet()) {
- final RemoteViews v = mIndexRemoteViews.get(i);
+ for (int i = mIndexRemoteViews.size() - 1; i >= 0; i--) {
+ final RemoteViews v = mIndexRemoteViews.valueAt(i);
if (v != null) {
mem += v.estimateMemoryUsage();
}
@@ -678,24 +635,25 @@
return mem;
}
- private int getFarthestPositionFrom(int pos, ArrayList<Integer> visibleWindow) {
+ private int getFarthestPositionFrom(int pos, int[] visibleWindow) {
// Find the index farthest away and remove that
int maxDist = 0;
int maxDistIndex = -1;
int maxDistNotVisible = 0;
int maxDistIndexNotVisible = -1;
- for (int i : mIndexRemoteViews.keySet()) {
- int dist = Math.abs(i-pos);
- if (dist > maxDistNotVisible && !visibleWindow.contains(i)) {
+ for (int i = mIndexRemoteViews.size() - 1; i >= 0; i--) {
+ int index = mIndexRemoteViews.keyAt(i);
+ int dist = Math.abs(index-pos);
+ if (dist > maxDistNotVisible && Arrays.binarySearch(visibleWindow, index) < 0) {
// maxDistNotVisible/maxDistIndexNotVisible will store the index of the
// farthest non-visible position
- maxDistIndexNotVisible = i;
+ maxDistIndexNotVisible = index;
maxDistNotVisible = dist;
}
if (dist >= maxDist) {
// maxDist/maxDistIndex will store the index of the farthest position
// regardless of whether it is visible or not
- maxDistIndex = i;
+ maxDistIndex = index;
maxDist = dist;
}
}
@@ -707,9 +665,8 @@
public void queueRequestedPositionToLoad(int position) {
mLastRequestedIndex = position;
- synchronized (mLoadIndices) {
- mRequestedIndices.add(position);
- mLoadIndices.add(position);
+ synchronized (mIndicesToLoad) {
+ mIndicesToLoad.put(position, true);
}
}
public boolean queuePositionsToBePreloadedFromRequestedPosition(int position) {
@@ -725,11 +682,13 @@
synchronized (mMetaData) {
count = mMetaData.count;
}
- synchronized (mLoadIndices) {
- mLoadIndices.clear();
-
- // Add all the requested indices
- mLoadIndices.addAll(mRequestedIndices);
+ synchronized (mIndicesToLoad) {
+ // Remove all indices which have not been previously requested.
+ for (int i = mIndicesToLoad.size() - 1; i >= 0; i--) {
+ if (!mIndicesToLoad.valueAt(i)) {
+ mIndicesToLoad.removeAt(i);
+ }
+ }
// Add all the preload indices
int halfMaxCount = mMaxCount / 2;
@@ -738,43 +697,40 @@
int effectiveLowerBound = Math.max(0, mPreloadLowerBound);
int effectiveUpperBound = Math.min(mPreloadUpperBound, count - 1);
for (int i = effectiveLowerBound; i <= effectiveUpperBound; ++i) {
- mLoadIndices.add(i);
+ if (mIndexRemoteViews.indexOfKey(i) < 0 && !mIndicesToLoad.get(i)) {
+ // If the index has not been requested, and has not been loaded.
+ mIndicesToLoad.put(i, false);
+ }
}
-
- // But remove all the indices that have already been loaded and are cached
- mLoadIndices.removeAll(mIndexRemoteViews.keySet());
}
return true;
}
- /** Returns the next index to load, and whether that index was directly requested or not */
- public int[] getNextIndexToLoad() {
+ /** Returns the next index to load */
+ public int getNextIndexToLoad() {
// We try and prioritize items that have been requested directly, instead
// of items that are loaded as a result of the caching mechanism
- synchronized (mLoadIndices) {
+ synchronized (mIndicesToLoad) {
// Prioritize requested indices to be loaded first
- if (!mRequestedIndices.isEmpty()) {
- Integer i = mRequestedIndices.iterator().next();
- mRequestedIndices.remove(i);
- mLoadIndices.remove(i);
- return new int[]{i.intValue(), 1};
+ int index = mIndicesToLoad.indexOfValue(true);
+ if (index < 0) {
+ // Otherwise, preload other indices as necessary
+ index = mIndicesToLoad.indexOfValue(false);
}
-
- // Otherwise, preload other indices as necessary
- if (!mLoadIndices.isEmpty()) {
- Integer i = mLoadIndices.iterator().next();
- mLoadIndices.remove(i);
- return new int[]{i.intValue(), 0};
+ if (index < 0) {
+ return -1;
+ } else {
+ int key = mIndicesToLoad.keyAt(index);
+ mIndicesToLoad.removeAt(index);
+ return key;
}
-
- return new int[]{-1, 0};
}
}
public boolean containsRemoteViewAt(int position) {
- return mIndexRemoteViews.containsKey(position);
+ return mIndexRemoteViews.indexOfKey(position) >= 0;
}
public boolean containsMetaDataAt(int position) {
- return mIndexMetaData.containsKey(position);
+ return mIndexMetaData.indexOfKey(position) >= 0;
}
public void reset() {
@@ -787,9 +743,8 @@
mLastRequestedIndex = -1;
mIndexRemoteViews.clear();
mIndexMetaData.clear();
- synchronized (mLoadIndices) {
- mRequestedIndices.clear();
- mLoadIndices.clear();
+ synchronized (mIndicesToLoad) {
+ mIndicesToLoad.clear();
}
}
}
@@ -942,8 +897,7 @@
// Get the next index to load
int position = -1;
synchronized (mCache) {
- int[] res = mCache.getNextIndexToLoad();
- position = res[0];
+ position = mCache.getNextIndexToLoad();
}
if (position > -1) {
// Load the item, and notify any existing RemoteViewsFrameLayouts
@@ -1048,7 +1002,7 @@
}
synchronized (mCache) {
if (viewTypeInRange) {
- ArrayList<Integer> visibleWindow = getVisibleWindow(mVisibleWindowLowerBound,
+ int[] visibleWindow = getVisibleWindow(mVisibleWindowLowerBound,
mVisibleWindowUpperBound, cacheCount);
// Cache the RemoteViews we loaded
mCache.insert(position, remoteViews, itemId, visibleWindow);
@@ -1117,21 +1071,6 @@
}
/**
- * Returns the item type id for the specified convert view. Returns -1 if the convert view
- * is invalid.
- */
- private int getConvertViewTypeId(View convertView) {
- int typeId = -1;
- if (convertView != null) {
- Object tag = convertView.getTag(com.android.internal.R.id.rowTypeId);
- if (tag != null) {
- typeId = (Integer) tag;
- }
- }
- return typeId;
- }
-
- /**
* This method allows an AdapterView using this Adapter to provide information about which
* views are currently being displayed. This allows for certain optimizations and preloading
* which wouldn't otherwise be possible.
@@ -1145,7 +1084,8 @@
// "Request" an index so that we can queue it for loading, initiate subsequent
// preloading, etc.
synchronized (mCache) {
- boolean isInCache = mCache.containsRemoteViewAt(position);
+ RemoteViews rv = mCache.getRemoteViewsAt(position);
+ boolean isInCache = (rv != null);
boolean isConnected = mServiceConnection.isConnected();
boolean hasNewItems = false;
@@ -1162,75 +1102,23 @@
hasNewItems = mCache.queuePositionsToBePreloadedFromRequestedPosition(position);
}
+ final RemoteViewsFrameLayout layout =
+ (convertView instanceof RemoteViewsFrameLayout)
+ ? (RemoteViewsFrameLayout) convertView
+ : new RemoteViewsFrameLayout(parent.getContext(), mCache);
if (isInCache) {
- View convertViewChild = null;
- int convertViewTypeId = 0;
- RemoteViewsFrameLayout layout = null;
-
- if (convertView instanceof RemoteViewsFrameLayout) {
- layout = (RemoteViewsFrameLayout) convertView;
- convertViewChild = layout.getChildAt(0);
- convertViewTypeId = getConvertViewTypeId(convertViewChild);
- }
-
- // Second, we try and retrieve the RemoteViews from the cache, returning a loading
- // view and queueing it to be loaded if it has not already been loaded.
- Context context = parent.getContext();
- RemoteViews rv = mCache.getRemoteViewsAt(position);
- RemoteViewsIndexMetaData indexMetaData = mCache.getMetaDataAt(position);
- int typeId = indexMetaData.typeId;
-
- try {
- // Reuse the convert view where possible
- if (layout != null) {
- if (convertViewTypeId == typeId) {
- rv.reapply(context, convertViewChild, mRemoteViewsOnClickHandler);
- return layout;
- }
- layout.removeAllViews();
- } else {
- layout = new RemoteViewsFrameLayout(context);
- }
-
- // Otherwise, create a new view to be returned
- View newView = rv.apply(context, parent, mRemoteViewsOnClickHandler);
- newView.setTagInternal(com.android.internal.R.id.rowTypeId,
- new Integer(typeId));
- layout.addView(newView);
- return layout;
-
- } catch (Exception e){
- // We have to make sure that we successfully inflated the RemoteViews, if not
- // we return the loading view instead.
- Log.w(TAG, "Error inflating RemoteViews at position: " + position + ", using" +
- "loading view instead" + e);
-
- RemoteViewsFrameLayout loadingView = null;
- final RemoteViewsMetaData metaData = mCache.getMetaData();
- synchronized (metaData) {
- loadingView = metaData.createLoadingView(position, convertView, parent,
- mCache, mLayoutInflater, mRemoteViewsOnClickHandler);
- }
- return loadingView;
- } finally {
- if (hasNewItems) loadNextIndexInBackground();
- }
+ layout.onRemoteViewsLoaded(rv, mRemoteViewsOnClickHandler);
+ if (hasNewItems) loadNextIndexInBackground();
} else {
- // If the cache does not have the RemoteViews at this position, then create a
- // loading view and queue the actual position to be loaded in the background
- RemoteViewsFrameLayout loadingView = null;
- final RemoteViewsMetaData metaData = mCache.getMetaData();
- synchronized (metaData) {
- loadingView = metaData.createLoadingView(position, convertView, parent,
- mCache, mLayoutInflater, mRemoteViewsOnClickHandler);
- }
-
- mRequestedViews.add(position, loadingView);
+ // If the views is not loaded, apply the loading view. If the loading view doesn't
+ // exist, the layout will create a default view based on the firstView height.
+ layout.onRemoteViewsLoaded(mCache.getMetaData().mUserLoadingView,
+ mRemoteViewsOnClickHandler);
+ mRequestedViews.add(position, layout);
mCache.queueRequestedPositionToLoad(position);
loadNextIndexInBackground();
-
- return loadingView;
}
+ return layout;
}
}
@@ -1276,7 +1164,7 @@
// Re-request the new metadata (only after the notification to the factory)
updateTemporaryMetaData();
int newCount;
- ArrayList<Integer> visibleWindow;
+ int[] visibleWindow;
synchronized(mCache.getTemporaryMetaData()) {
newCount = mCache.getTemporaryMetaData().count;
visibleWindow = getVisibleWindow(mVisibleWindowLowerBound,
@@ -1311,26 +1199,33 @@
mNotifyDataSetChangedAfterOnServiceConnected = false;
}
- private ArrayList<Integer> getVisibleWindow(int lower, int upper, int count) {
- ArrayList<Integer> window = new ArrayList<Integer>();
-
+ /**
+ * Returns a sorted array of all integers between lower and upper.
+ */
+ private int[] getVisibleWindow(int lower, int upper, int count) {
// In the case that the window is invalid or uninitialized, return an empty window.
if ((lower == 0 && upper == 0) || lower < 0 || upper < 0) {
- return window;
+ return new int[0];
}
+ int[] window;
if (lower <= upper) {
- for (int i = lower; i <= upper; i++){
- window.add(i);
+ window = new int[upper + 1 - lower];
+ for (int i = lower, j = 0; i <= upper; i++, j++){
+ window[j] = i;
}
} else {
// If the upper bound is less than the lower bound it means that the visible window
// wraps around.
- for (int i = lower; i < count; i++) {
- window.add(i);
+ count = Math.max(count, lower);
+ window = new int[count - lower + upper + 1];
+ int j = 0;
+ // Add the entries in sorted order
+ for (int i = 0; i <= upper; i++, j++) {
+ window[j] = i;
}
- for (int i = 0; i <= upper; i++) {
- window.add(i);
+ for (int i = lower; i < count; i++, j++) {
+ window[j] = i;
}
}
return window;
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/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_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/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/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/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/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/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 16e1c5c..da4a038 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -1940,6 +1940,15 @@
* </ul>
*/
public void onHardwareVideoUnavailable(int reason) { }
+
+ @Override
+ void release() {
+ if (mHardwareSession != null) {
+ mHardwareSession.release();
+ mHardwareSession = null;
+ }
+ super.release();
+ }
}
/** @hide */
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/res/drawable/ic_root_home.xml b/packages/DocumentsUI/res/drawable/ic_root_documents.xml
similarity index 86%
rename from packages/DocumentsUI/res/drawable/ic_root_home.xml
rename to packages/DocumentsUI/res/drawable/ic_root_documents.xml
index 696ee05..afd886d 100644
--- a/packages/DocumentsUI/res/drawable/ic_root_home.xml
+++ b/packages/DocumentsUI/res/drawable/ic_root_documents.xml
@@ -20,5 +20,5 @@
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
- android:pathData="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
+ android:pathData="M10 4H4c-1.1 0,-1.99.9,-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2,-.9 2,-2V8c0,-1.1,-.9,-2,-2,-2h-8l-2,-2z"/>
</vector>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index 470989d..648c79e 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -23,7 +23,6 @@
import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_NONE;
import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_SIDE;
import static com.android.internal.util.Preconditions.checkArgument;
-import static com.android.internal.util.Preconditions.checkState;
import android.app.Activity;
import android.app.Fragment;
@@ -464,8 +463,7 @@
* Set mode based on explicit user action.
*/
void setViewMode(@ViewMode int mode) {
- checkState(mState.stack.root != null);
- LocalPreferences.setViewMode(this, mState.stack.root, mode);
+ LocalPreferences.setViewMode(this, getCurrentRoot(), mode);
mState.derivedMode = mode;
// view icon needs to be updated, but we *could* do it
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/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 4233b36..0ae2a5c 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -536,7 +536,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
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 3f14a55..f4a97be 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
@@ -171,7 +171,7 @@
// TODO: remove these special case icons
if (isHome()) {
- derivedIcon = R.drawable.ic_root_home;
+ derivedIcon = R.drawable.ic_root_documents;
derivedType = TYPE_LOCAL;
} else if (isExternalStorage()) {
derivedIcon = R.drawable.ic_root_smartphone;
@@ -276,12 +276,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/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/ExternalStorageProvider/res/values/strings.xml b/packages/ExternalStorageProvider/res/values/strings.xml
index e48436e..8b16d3c 100644
--- a/packages/ExternalStorageProvider/res/values/strings.xml
+++ b/packages/ExternalStorageProvider/res/values/strings.xml
@@ -20,6 +20,6 @@
<!-- Title for documents backend that offers internal storage. [CHAR LIMIT=24] -->
<string name="root_internal_storage">Internal storage</string>
- <!-- Title for user home dir. [CHAR LIMIT=24] -->
- <string name="root_home">Home</string>
+ <!-- Title for directory in which a user may store their own documents and files. [CHAR LIMIT=24] -->
+ <string name="root_documents">Documents</string>
</resources>
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index f89934d..97dfd47 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -199,7 +199,7 @@
final RootInfo root = new RootInfo();
root.rootId = ROOT_ID_HOME;
mRoots.put(root.rootId, root);
- root.title = getContext().getString(R.string.root_home);
+ root.title = getContext().getString(R.string.root_documents);
// Only report bytes on *volumes*...as a matter of policy.
root.reportAvailableBytes = false;
@@ -214,9 +214,9 @@
// Create the "Home" directory on disk, but don't the localized root.title
// since the directories shouldn't be localized.
root.visiblePath = new File(
- primaryVolume.getPathForUser(userId), Environment.DIRECTORY_HOME);
+ primaryVolume.getPathForUser(userId), Environment.DIRECTORY_DOCUMENTS);
root.path = new File(
- primaryVolume.getInternalPathForUser(userId), root.rootId);
+ primaryVolume.getInternalPathForUser(userId), Environment.DIRECTORY_DOCUMENTS);
try {
root.docId = getDocIdForFile(root.path);
} catch (FileNotFoundException e) {
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..26152cd 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -34,19 +34,31 @@
<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,18 +80,6 @@
</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
- <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>
-
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
@@ -104,44 +104,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_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_height="19dp"
android:layout_marginTop="4dp"
- android:id="@+id/clock" />
+ 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 +170,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/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 11c13e1..aed5ab2 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -162,7 +162,10 @@
<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_page_indicator_size">12dp</dimen>
<dimen name="qs_tile_icon_size">24dp</dimen>
<dimen name="qs_tile_text_size">12sp</dimen>
@@ -598,9 +601,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/src/com/android/systemui/EventLogConstants.java b/packages/SystemUI/src/com/android/systemui/EventLogConstants.java
index 43a1be1..9238928 100644
--- a/packages/SystemUI/src/com/android/systemui/EventLogConstants.java
+++ b/packages/SystemUI/src/com/android/systemui/EventLogConstants.java
@@ -40,4 +40,15 @@
public static final int SYSUI_SHADE_GESTURE_SWIPE_DOWN_QS = 9;
/** The user tapped on the status bar to open quick settings, from shade. */
public static final int SYSUI_TAP_TO_OPEN_QS = 10;
+
+ /** Secondary user tries binding to the system sysui service */
+ public static final int SYSUI_RECENTS_CONNECTION_USER_BIND_SERVICE = 1;
+ /** Secondary user is bound to the system sysui service */
+ public static final int SYSUI_RECENTS_CONNECTION_USER_SYSTEM_BOUND = 2;
+ /** Secondary user loses connection after system sysui has died */
+ public static final int SYSUI_RECENTS_CONNECTION_USER_SYSTEM_UNBOUND = 3;
+ /** System sysui registers secondary user's callbacks */
+ public static final int SYSUI_RECENTS_CONNECTION_SYSTEM_REGISTER_USER = 4;
+ /** System sysui unregisters secondary user's callbacks (after death) */
+ public static final int SYSUI_RECENTS_CONNECTION_SYSTEM_UNREGISTER_USER = 5;
}
diff --git a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
index a584cf6..1601675 100644
--- a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
+++ b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
@@ -51,3 +51,13 @@
# SearchPanelView.java
# ---------------------------
36050 sysui_searchpanel_touch (type|1),(x|1),(y|1)
+
+# ---------------------------
+# Recents.java, RecentsSystemUser.java
+# ---------------------------
+## type: 1: USER_BIND_SERVICE Secondary user tries binding to the system sysui service
+## 2: USER_SYSTEM_BOUND Secondary user is bound to the system sysui service
+## 3: USER_SYSTEM_UNBOUND Secondary user loses connection after system sysui has died
+## 4: SYSTEM_REGISTER_USER System sysui registers user's callbacks
+## 5: SYSTEM_UNREGISTER_USER System sysui unregisters user's callbacks (after death)
+36060 sysui_recents_connection (type|1),(user|1)
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index e4fd31d..f5ae351 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -31,10 +31,13 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
+import android.util.EventLog;
import android.util.Log;
import android.view.Display;
import android.view.View;
+import com.android.systemui.EventLogConstants;
+import com.android.systemui.EventLogTags;
import com.android.systemui.RecentsComponent;
import com.android.systemui.SystemUI;
import com.android.systemui.recents.events.EventBus;
@@ -83,20 +86,23 @@
private int mDraggingInRecentsCurrentUser;
// Only For system user, this is the callbacks instance we return to each secondary user
- private RecentsSystemUser mSystemUserCallbacks;
+ private RecentsSystemUser mSystemToUserCallbacks;
// Only for secondary users, this is the callbacks instance provided by the system user to make
// calls back
- private IRecentsSystemUserCallbacks mCallbacksToSystemUser;
+ private IRecentsSystemUserCallbacks mUserToSystemCallbacks;
// The set of runnables to run after binding to the system user's service.
private final ArrayList<Runnable> mOnConnectRunnables = new ArrayList<>();
// Only for secondary users, this is the death handler for the binder from the system user
- private final IBinder.DeathRecipient mCallbacksToSystemUserDeathRcpt = new IBinder.DeathRecipient() {
+ private final IBinder.DeathRecipient mUserToSystemCallbacksDeathRcpt = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
- mCallbacksToSystemUser = null;
+ mUserToSystemCallbacks = null;
+ EventLog.writeEvent(EventLogTags.SYSUI_RECENTS_CONNECTION,
+ EventLogConstants.SYSUI_RECENTS_CONNECTION_USER_SYSTEM_UNBOUND,
+ sSystemServicesProxy.getProcessUser());
// Retry after a fixed duration
mHandler.postDelayed(new Runnable() {
@@ -109,16 +115,19 @@
};
// Only for secondary users, this is the service connection we use to connect to the system user
- private final ServiceConnection mServiceConnectionToSystemUser = new ServiceConnection() {
+ private final ServiceConnection mUserToSystemServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (service != null) {
- mCallbacksToSystemUser = IRecentsSystemUserCallbacks.Stub.asInterface(
+ mUserToSystemCallbacks = IRecentsSystemUserCallbacks.Stub.asInterface(
service);
+ EventLog.writeEvent(EventLogTags.SYSUI_RECENTS_CONNECTION,
+ EventLogConstants.SYSUI_RECENTS_CONNECTION_USER_SYSTEM_BOUND,
+ sSystemServicesProxy.getProcessUser());
// Listen for system user's death, so that we can reconnect later
try {
- service.linkToDeath(mCallbacksToSystemUserDeathRcpt, 0);
+ service.linkToDeath(mUserToSystemCallbacksDeathRcpt, 0);
} catch (RemoteException e) {
Log.e(TAG, "Lost connection to (System) SystemUI", e);
}
@@ -142,7 +151,7 @@
* Returns the callbacks interface that non-system users can call.
*/
public IBinder getSystemUserCallbacks() {
- return mSystemUserCallbacks;
+ return mSystemToUserCallbacks;
}
public static RecentsTaskLoader getTaskLoader() {
@@ -190,7 +199,7 @@
if (sSystemServicesProxy.isSystemUser(processUser)) {
// For the system user, initialize an instance of the interface that we can pass to the
// secondary user
- mSystemUserCallbacks = new RecentsSystemUser(mContext, mImpl);
+ mSystemToUserCallbacks = new RecentsSystemUser(mContext, mImpl);
} else {
// For the secondary user, bind to the primary user's service to get a persistent
// interface to register its implementation and to later update its state
@@ -224,9 +233,9 @@
mImpl.showRecents(triggeredFromAltTab, false /* draggingInRecents */,
true /* animate */, false /* reloadTasks */);
} else {
- if (mSystemUserCallbacks != null) {
+ if (mSystemToUserCallbacks != null) {
IRecentsNonSystemUserCallbacks callbacks =
- mSystemUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
+ mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
if (callbacks != null) {
try {
callbacks.showRecents(triggeredFromAltTab, false /* draggingInRecents */,
@@ -260,9 +269,9 @@
if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.hideRecents(triggeredFromAltTab, triggeredFromHomeKey);
} else {
- if (mSystemUserCallbacks != null) {
+ if (mSystemToUserCallbacks != null) {
IRecentsNonSystemUserCallbacks callbacks =
- mSystemUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
+ mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
if (callbacks != null) {
try {
callbacks.hideRecents(triggeredFromAltTab, triggeredFromHomeKey);
@@ -295,9 +304,9 @@
if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.toggleRecents();
} else {
- if (mSystemUserCallbacks != null) {
+ if (mSystemToUserCallbacks != null) {
IRecentsNonSystemUserCallbacks callbacks =
- mSystemUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
+ mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
if (callbacks != null) {
try {
callbacks.toggleRecents();
@@ -326,9 +335,9 @@
if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.preloadRecents();
} else {
- if (mSystemUserCallbacks != null) {
+ if (mSystemToUserCallbacks != null) {
IRecentsNonSystemUserCallbacks callbacks =
- mSystemUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
+ mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
if (callbacks != null) {
try {
callbacks.preloadRecents();
@@ -354,9 +363,9 @@
if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.cancelPreloadingRecents();
} else {
- if (mSystemUserCallbacks != null) {
+ if (mSystemToUserCallbacks != null) {
IRecentsNonSystemUserCallbacks callbacks =
- mSystemUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
+ mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
if (callbacks != null) {
try {
callbacks.cancelPreloadingRecents();
@@ -387,9 +396,9 @@
if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.dockTopTask(topTask.id, dragMode, stackCreateMode, initialBounds);
} else {
- if (mSystemUserCallbacks != null) {
+ if (mSystemToUserCallbacks != null) {
IRecentsNonSystemUserCallbacks callbacks =
- mSystemUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
+ mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
if (callbacks != null) {
try {
callbacks.dockTopTask(topTask.id, dragMode, stackCreateMode,
@@ -413,9 +422,9 @@
if (sSystemServicesProxy.isSystemUser(mDraggingInRecentsCurrentUser)) {
mImpl.onDraggingInRecents(distanceFromTop);
} else {
- if (mSystemUserCallbacks != null) {
+ if (mSystemToUserCallbacks != null) {
IRecentsNonSystemUserCallbacks callbacks =
- mSystemUserCallbacks.getNonSystemUserRecentsForUser(
+ mSystemToUserCallbacks.getNonSystemUserRecentsForUser(
mDraggingInRecentsCurrentUser);
if (callbacks != null) {
try {
@@ -436,9 +445,9 @@
if (sSystemServicesProxy.isSystemUser(mDraggingInRecentsCurrentUser)) {
mImpl.onDraggingInRecentsEnded(velocity);
} else {
- if (mSystemUserCallbacks != null) {
+ if (mSystemToUserCallbacks != null) {
IRecentsNonSystemUserCallbacks callbacks =
- mSystemUserCallbacks.getNonSystemUserRecentsForUser(
+ mSystemToUserCallbacks.getNonSystemUserRecentsForUser(
mDraggingInRecentsCurrentUser);
if (callbacks != null) {
try {
@@ -484,9 +493,9 @@
if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.onConfigurationChanged();
} else {
- if (mSystemUserCallbacks != null) {
+ if (mSystemToUserCallbacks != null) {
IRecentsNonSystemUserCallbacks callbacks =
- mSystemUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
+ mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
if (callbacks != null) {
try {
callbacks.onConfigurationChanged();
@@ -512,7 +521,7 @@
@Override
public void run() {
try {
- mCallbacksToSystemUser.updateRecentsVisibility(event.visible);
+ mUserToSystemCallbacks.updateRecentsVisibility(event.visible);
} catch (RemoteException e) {
Log.e(TAG, "Callback failed", e);
}
@@ -533,7 +542,7 @@
@Override
public void run() {
try {
- mCallbacksToSystemUser.startScreenPinning();
+ mUserToSystemCallbacks.startScreenPinning();
} catch (RemoteException e) {
Log.e(TAG, "Callback failed", e);
}
@@ -549,7 +558,7 @@
@Override
public void run() {
try {
- mCallbacksToSystemUser.sendRecentsDrawnEvent();
+ mUserToSystemCallbacks.sendRecentsDrawnEvent();
} catch (RemoteException e) {
Log.e(TAG, "Callback failed", e);
}
@@ -565,7 +574,7 @@
@Override
public void run() {
try {
- mCallbacksToSystemUser.sendDockingTopTaskEvent(event.dragMode);
+ mUserToSystemCallbacks.sendDockingTopTaskEvent(event.dragMode);
} catch (RemoteException e) {
Log.e(TAG, "Callback failed", e);
}
@@ -581,7 +590,7 @@
@Override
public void run() {
try {
- mCallbacksToSystemUser.sendLaunchRecentsEvent();
+ mUserToSystemCallbacks.sendLaunchRecentsEvent();
} catch (RemoteException e) {
Log.e(TAG, "Callback failed", e);
}
@@ -599,7 +608,7 @@
@Override
public void run() {
try {
- mCallbacksToSystemUser.registerNonSystemUserCallbacks(
+ mUserToSystemCallbacks.registerNonSystemUserCallbacks(
new RecentsImplProxy(mImpl), processUser);
} catch (RemoteException e) {
Log.e(TAG, "Failed to register", e);
@@ -614,11 +623,14 @@
*/
private void postToSystemUser(final Runnable onConnectRunnable) {
mOnConnectRunnables.add(onConnectRunnable);
- if (mCallbacksToSystemUser == null) {
+ if (mUserToSystemCallbacks == null) {
Intent systemUserServiceIntent = new Intent();
systemUserServiceIntent.setClass(mContext, RecentsSystemUserService.class);
boolean bound = mContext.bindServiceAsUser(systemUserServiceIntent,
- mServiceConnectionToSystemUser, Context.BIND_AUTO_CREATE, UserHandle.SYSTEM);
+ mUserToSystemServiceConnection, Context.BIND_AUTO_CREATE, UserHandle.SYSTEM);
+ EventLog.writeEvent(EventLogTags.SYSUI_RECENTS_CONNECTION,
+ EventLogConstants.SYSUI_RECENTS_CONNECTION_USER_BIND_SERVICE,
+ sSystemServicesProxy.getProcessUser());
if (!bound) {
// Retry after a fixed duration
mHandler.postDelayed(new Runnable() {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 9d4f425..c2a6108 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -230,7 +230,7 @@
* Dismisses the history view back into the stack view.
*/
boolean dismissHistory() {
- if (mRecentsView.isHistoryVisible()) {
+ if (RecentsDebugFlags.Static.EnableHistory && mRecentsView.isHistoryVisible()) {
EventBus.getDefault().send(new HideHistoryEvent(true /* animate */));
return true;
}
@@ -447,7 +447,7 @@
// Reset some states
mIgnoreAltTabRelease = false;
- if (mRecentsView.isHistoryVisible()) {
+ if (RecentsDebugFlags.Static.EnableHistory && mRecentsView.isHistoryVisible()) {
EventBus.getDefault().send(new HideHistoryEvent(false /* animate */));
}
@@ -503,13 +503,16 @@
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
- outState.putBoolean(KEY_SAVED_STATE_HISTORY_VISIBLE, mRecentsView.isHistoryVisible());
+ if (RecentsDebugFlags.Static.EnableHistory) {
+ outState.putBoolean(KEY_SAVED_STATE_HISTORY_VISIBLE, mRecentsView.isHistoryVisible());
+ }
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
- if (savedInstanceState.getBoolean(KEY_SAVED_STATE_HISTORY_VISIBLE, false)) {
+ if (RecentsDebugFlags.Static.EnableHistory &&
+ savedInstanceState.getBoolean(KEY_SAVED_STATE_HISTORY_VISIBLE, false)) {
EventBus.getDefault().send(new ShowHistoryEvent());
}
}
@@ -603,7 +606,7 @@
/**** EventBus events ****/
public final void onBusEvent(ToggleRecentsEvent event) {
- if (!dismissHistory()) {
+ if (!RecentsDebugFlags.Static.EnableHistory || !dismissHistory()) {
RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
if (launchState.launchedFromHome) {
dismissRecentsToHome(true /* animateTaskViews */);
@@ -614,7 +617,7 @@
}
public final void onBusEvent(IterateRecentsEvent event) {
- if (!dismissHistory()) {
+ if (!RecentsDebugFlags.Static.EnableHistory || !dismissHistory()) {
final RecentsDebugFlags debugFlags = Recents.getDebugFlags();
// Start dozing after the recents button is clicked
@@ -651,7 +654,7 @@
}
} else if (event.triggeredFromHomeKey) {
// Otherwise, dismiss Recents to Home
- if (mRecentsView.isHistoryVisible()) {
+ if (RecentsDebugFlags.Static.EnableHistory && mRecentsView.isHistoryVisible()) {
// If the history view is visible, then just cross-fade home
ActivityOptions opts = ActivityOptions.makeCustomAnimation(RecentsActivity.this,
R.anim.recents_to_launcher_enter,
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
index 711d834..cd64323 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
@@ -37,6 +37,8 @@
public static final boolean DisableBackgroundCache = false;
// Enables the task affiliations
public static final boolean EnableAffiliatedTaskGroups = true;
+ // Enables the history
+ public static final boolean EnableHistory = false;
// Overrides the Tuner flags and enables the fast toggle and timeout
public static final boolean EnableFastToggleTimeoutOverride = true;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index e0efaa5..8de964b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -406,9 +406,9 @@
SystemServicesProxy ssp = Recents.getSystemServices();
ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
MutableBoolean topTaskHome = new MutableBoolean(true);
- RecentsTaskLoader loader = Recents.getTaskLoader();
- sInstanceLoadPlan = loader.createLoadPlan(mContext);
if (topTask != null && !ssp.isRecentsTopMost(topTask, topTaskHome)) {
+ RecentsTaskLoader loader = Recents.getTaskLoader();
+ sInstanceLoadPlan = loader.createLoadPlan(mContext);
sInstanceLoadPlan.preloadRawTasks(topTaskHome.value);
loader.preloadTasks(sInstanceLoadPlan, topTask.id, topTaskHome.value);
TaskStack stack = sInstanceLoadPlan.getTaskStack();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java
index ae0051c..f8000b8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java
@@ -19,9 +19,12 @@
import android.content.Context;
import android.os.IBinder;
import android.os.RemoteException;
+import android.util.EventLog;
import android.util.Log;
import android.util.SparseArray;
+import com.android.systemui.EventLogConstants;
+import com.android.systemui.EventLogTags;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.DockingTopTaskEvent;
import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
@@ -46,7 +49,8 @@
}
@Override
- public void registerNonSystemUserCallbacks(final IBinder nonSystemUserCallbacks, int userId) {
+ public void registerNonSystemUserCallbacks(final IBinder nonSystemUserCallbacks,
+ final int userId) {
try {
final IRecentsNonSystemUserCallbacks callback =
IRecentsNonSystemUserCallbacks.Stub.asInterface(nonSystemUserCallbacks);
@@ -54,9 +58,14 @@
@Override
public void binderDied() {
mNonSystemUserRecents.removeAt(mNonSystemUserRecents.indexOfValue(callback));
+ EventLog.writeEvent(EventLogTags.SYSUI_RECENTS_CONNECTION,
+ EventLogConstants.SYSUI_RECENTS_CONNECTION_SYSTEM_UNREGISTER_USER,
+ userId);
}
}, 0);
mNonSystemUserRecents.put(userId, callback);
+ EventLog.writeEvent(EventLogTags.SYSUI_RECENTS_CONNECTION,
+ EventLogConstants.SYSUI_RECENTS_CONNECTION_SYSTEM_REGISTER_USER, userId);
} catch (RemoteException e) {
Log.e(TAG, "Failed to register NonSystemUserCallbacks", e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index c4db485..2e45627 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -143,21 +143,24 @@
final float cornerRadius = context.getResources().getDimensionPixelSize(
R.dimen.recents_task_view_rounded_corners_radius);
LayoutInflater inflater = LayoutInflater.from(context);
- mHistoryButton = (TextView) inflater.inflate(R.layout.recents_history_button, this, false);
- mHistoryButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- EventBus.getDefault().send(new ToggleHistoryEvent());
- }
- });
- addView(mHistoryButton);
- mHistoryButton.setClipToOutline(true);
- mHistoryButton.setOutlineProvider(new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), cornerRadius);
- }
- });
+ if (RecentsDebugFlags.Static.EnableHistory) {
+ mHistoryButton = (TextView) inflater.inflate(R.layout.recents_history_button, this,
+ false);
+ mHistoryButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ EventBus.getDefault().send(new ToggleHistoryEvent());
+ }
+ });
+ addView(mHistoryButton);
+ mHistoryButton.setClipToOutline(true);
+ mHistoryButton.setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), cornerRadius);
+ }
+ });
+ }
mEmptyView = inflater.inflate(R.layout.recents_empty, this, false);
addView(mEmptyView);
@@ -331,7 +334,9 @@
mTaskStackView.setVisibility(View.INVISIBLE);
mEmptyView.setVisibility(View.VISIBLE);
mEmptyView.bringToFront();
- mHistoryButton.bringToFront();
+ if (RecentsDebugFlags.Static.EnableHistory) {
+ mHistoryButton.bringToFront();
+ }
}
/**
@@ -347,7 +352,9 @@
if (mSearchBar != null) {
mSearchBar.bringToFront();
}
- mHistoryButton.bringToFront();
+ if (RecentsDebugFlags.Static.EnableHistory) {
+ mHistoryButton.bringToFront();
+ }
}
@Override
@@ -397,21 +404,23 @@
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
}
- // Measure the history view
- if (mHistoryView != null && mHistoryView.getVisibility() != GONE) {
- measureChild(mHistoryView, MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
- }
+ if (RecentsDebugFlags.Static.EnableHistory) {
+ // Measure the history view
+ if (mHistoryView != null && mHistoryView.getVisibility() != GONE) {
+ measureChild(mHistoryView, MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
+ }
- // Measure the history button within the constraints of the space above the stack
- Rect historyButtonRect = mTaskStackView.mLayoutAlgorithm.mHistoryButtonRect;
- measureChild(mHistoryButton,
- MeasureSpec.makeMeasureSpec(historyButtonRect.width(), MeasureSpec.AT_MOST),
- MeasureSpec.makeMeasureSpec(historyButtonRect.height(), MeasureSpec.AT_MOST));
- if (mHistoryClearAllButton != null && mHistoryClearAllButton.getVisibility() != GONE) {
- measureChild(mHistoryClearAllButton,
+ // Measure the history button within the constraints of the space above the stack
+ Rect historyButtonRect = mTaskStackView.mLayoutAlgorithm.mHistoryButtonRect;
+ measureChild(mHistoryButton,
MeasureSpec.makeMeasureSpec(historyButtonRect.width(), MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(historyButtonRect.height(), MeasureSpec.AT_MOST));
+ if (mHistoryClearAllButton != null && mHistoryClearAllButton.getVisibility() != GONE) {
+ measureChild(mHistoryClearAllButton,
+ MeasureSpec.makeMeasureSpec(historyButtonRect.width(), MeasureSpec.AT_MOST),
+ MeasureSpec.makeMeasureSpec(historyButtonRect.height(), MeasureSpec.AT_MOST));
+ }
}
setMeasuredDimension(width, height);
@@ -443,36 +452,39 @@
mEmptyView.layout(left, top, right, bottom);
}
- // Layout the history view
- if (mHistoryView != null && mHistoryView.getVisibility() != GONE) {
- mHistoryView.layout(left, top, right, bottom);
- }
+ if (RecentsDebugFlags.Static.EnableHistory) {
+ // Layout the history view
+ if (mHistoryView != null && mHistoryView.getVisibility() != GONE) {
+ mHistoryView.layout(left, top, right, bottom);
+ }
- // Layout the history button such that its drawable is start-aligned with the stack,
- // vertically centered in the available space above the stack
- Rect historyButtonRect = mTaskStackView.mLayoutAlgorithm.mHistoryButtonRect;
- int historyLeft = isLayoutRtl()
- ? historyButtonRect.right + mHistoryButton.getPaddingStart()
- - mHistoryButton.getMeasuredWidth()
- : historyButtonRect.left - mHistoryButton.getPaddingStart();
- int historyTop = historyButtonRect.top +
- (historyButtonRect.height() - mHistoryButton.getMeasuredHeight()) / 2;
- mHistoryButton.layout(historyLeft, historyTop,
- historyLeft + mHistoryButton.getMeasuredWidth(),
- historyTop + mHistoryButton.getMeasuredHeight());
+ // Layout the history button such that its drawable is start-aligned with the stack,
+ // vertically centered in the available space above the stack
+ Rect historyButtonRect = mTaskStackView.mLayoutAlgorithm.mHistoryButtonRect;
+ int historyLeft = isLayoutRtl()
+ ? historyButtonRect.right + mHistoryButton.getPaddingStart()
+ - mHistoryButton.getMeasuredWidth()
+ : historyButtonRect.left - mHistoryButton.getPaddingStart();
+ int historyTop = historyButtonRect.top +
+ (historyButtonRect.height() - mHistoryButton.getMeasuredHeight()) / 2;
+ mHistoryButton.layout(historyLeft, historyTop,
+ historyLeft + mHistoryButton.getMeasuredWidth(),
+ historyTop + mHistoryButton.getMeasuredHeight());
- // Layout the history clear all button such that it is end-aligned with the stack,
- // vertically centered in the available space above the stack
- if (mHistoryClearAllButton != null && mHistoryClearAllButton.getVisibility() != GONE) {
- int clearAllLeft = isLayoutRtl()
- ? historyButtonRect.left - mHistoryClearAllButton.getPaddingStart()
- : historyButtonRect.right + mHistoryClearAllButton.getPaddingStart()
- - mHistoryClearAllButton.getMeasuredWidth();
- int clearAllTop = historyButtonRect.top +
- (historyButtonRect.height() - mHistoryClearAllButton.getMeasuredHeight()) / 2;
- mHistoryClearAllButton.layout(clearAllLeft, clearAllTop,
- clearAllLeft + mHistoryClearAllButton.getMeasuredWidth(),
- clearAllTop + mHistoryClearAllButton.getMeasuredHeight());
+ // Layout the history clear all button such that it is end-aligned with the stack,
+ // vertically centered in the available space above the stack
+ if (mHistoryClearAllButton != null && mHistoryClearAllButton.getVisibility() != GONE) {
+ int clearAllLeft = isLayoutRtl()
+ ? historyButtonRect.left - mHistoryClearAllButton.getPaddingStart()
+ : historyButtonRect.right + mHistoryClearAllButton.getPaddingStart()
+ - mHistoryClearAllButton.getMeasuredWidth();
+ int clearAllTop = historyButtonRect.top +
+ (historyButtonRect.height() - mHistoryClearAllButton.getMeasuredHeight()) /
+ 2;
+ mHistoryClearAllButton.layout(clearAllLeft, clearAllTop,
+ clearAllLeft + mHistoryClearAllButton.getMeasuredWidth(),
+ clearAllTop + mHistoryClearAllButton.getMeasuredHeight());
+ }
}
if (mAwaitingFirstLayout) {
@@ -540,9 +552,11 @@
}
public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) {
- // Hide the history button
int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION;
- hideHistoryButton(taskViewExitToHomeDuration, false /* translate */);
+ if (RecentsDebugFlags.Static.EnableHistory) {
+ // Hide the history button
+ hideHistoryButton(taskViewExitToHomeDuration, false /* translate */);
+ }
animateBackgroundScrim(0f, taskViewExitToHomeDuration);
}
@@ -675,11 +689,17 @@
// Reset the view state
mAwaitingFirstLayout = true;
mLastTaskLaunchedWasFreeform = false;
- hideHistoryButton(0, false /* translate */);
+ if (RecentsDebugFlags.Static.EnableHistory) {
+ hideHistoryButton(0, false /* translate */);
+ }
}
}
public final void onBusEvent(ToggleHistoryEvent event) {
+ if (!RecentsDebugFlags.Static.EnableHistory) {
+ return;
+ }
+
if (mHistoryView != null && mHistoryView.isVisible()) {
EventBus.getDefault().send(new HideHistoryEvent(true /* animate */));
} else {
@@ -688,6 +708,10 @@
}
public final void onBusEvent(ShowHistoryEvent event) {
+ if (!RecentsDebugFlags.Static.EnableHistory) {
+ return;
+ }
+
if (mHistoryView == null) {
LayoutInflater inflater = LayoutInflater.from(getContext());
mHistoryView = (RecentsHistoryView) inflater.inflate(R.layout.recents_history, this,
@@ -746,6 +770,10 @@
}
public final void onBusEvent(HideHistoryEvent event) {
+ if (!RecentsDebugFlags.Static.EnableHistory) {
+ return;
+ }
+
// Animate the empty view in parallel with the history view (the task view animations are
// handled in TaskStackView)
Rect stackRect = mTaskStackView.mLayoutAlgorithm.mStackRect;
@@ -765,10 +793,18 @@
}
public final void onBusEvent(ShowHistoryButtonEvent event) {
+ if (!RecentsDebugFlags.Static.EnableHistory) {
+ return;
+ }
+
showHistoryButton(150, event.translate);
}
public final void onBusEvent(HideHistoryButtonEvent event) {
+ if (!RecentsDebugFlags.Static.EnableHistory) {
+ return;
+ }
+
hideHistoryButton(100, true /* translate */);
}
@@ -776,6 +812,10 @@
* Shows the history button.
*/
private void showHistoryButton(final int duration, final boolean translate) {
+ if (!RecentsDebugFlags.Static.EnableHistory) {
+ return;
+ }
+
final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger();
if (mHistoryButton.getVisibility() == View.INVISIBLE) {
mHistoryButton.setVisibility(View.VISIBLE);
@@ -808,6 +848,10 @@
* Hides the history button.
*/
private void hideHistoryButton(int duration, boolean translate) {
+ if (!RecentsDebugFlags.Static.EnableHistory) {
+ return;
+ }
+
final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger();
hideHistoryButton(duration, translate, postAnimationTrigger);
postAnimationTrigger.flushLastDecrementRunnables();
@@ -818,6 +862,10 @@
*/
private void hideHistoryButton(int duration, boolean translate,
final ReferenceCountedTrigger postAnimationTrigger) {
+ if (!RecentsDebugFlags.Static.EnableHistory) {
+ return;
+ }
+
if (mHistoryButton.getVisibility() == View.VISIBLE) {
if (translate) {
mHistoryButton.animate()
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
index 890b445..2cd0c19 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
@@ -467,12 +467,13 @@
// Setup the end listener to return all the hidden views to the view pool after the
// focus animation
- AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() {
+ ReferenceCountedTrigger postAnimTrigger = new ReferenceCountedTrigger();
+ postAnimTrigger.addLastDecrementRunnable(new Runnable() {
@Override
- public void onAnimationEnd(Animator animation) {
+ public void run() {
mStackView.bindVisibleTaskViews(newScroll);
}
- };
+ });
List<TaskView> taskViews = mStackView.getTaskViews();
int taskViewCount = taskViews.size();
@@ -513,7 +514,8 @@
AnimationProps anim = new AnimationProps()
.setDuration(AnimationProps.BOUNDS, duration)
.setInterpolator(AnimationProps.BOUNDS, interpolator)
- .setListener(endListener);
+ .setListener(postAnimTrigger.decrementOnAnimationEnd());
+ postAnimTrigger.increment();
mStackView.updateTaskViewToTransform(tv, toTransform, anim);
}
return willScroll;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
index 79c21f3..33315c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -107,7 +107,7 @@
mPanel.setPanelScrimMinFraction((float) expandedHeight
/ mPanel.getMaxPanelHeight());
mPanel.startExpandMotion(x, y, true /* startTracking */, expandedHeight);
- mPanel.clearNotificattonEffects();
+ mPanel.clearNotificationEffects();
return true;
}
break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 88a7843..1a0acbe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -89,13 +89,13 @@
private KeyguardAffordanceHelper mAfforanceHelper;
private KeyguardUserSwitcher mKeyguardUserSwitcher;
private KeyguardStatusBarView mKeyguardStatusBar;
- private QSContainer mQsContainer;
+ protected QSContainer mQsContainer;
private KeyguardStatusView mKeyguardStatusView;
private TextView mClockView;
private View mReserveNotificationSpace;
private View mQsNavbarScrim;
private NotificationsQuickSettingsContainer mNotificationContainerParent;
- private NotificationStackScrollLayout mNotificationStackScroller;
+ protected NotificationStackScrollLayout mNotificationStackScroller;
private boolean mAnimateNextTopPaddingChange;
private int mTrackingPointer;
@@ -126,9 +126,9 @@
private float mInitialTouchY;
private float mLastTouchX;
private float mLastTouchY;
- private float mQsExpansionHeight;
- private int mQsMinExpansionHeight;
- private int mQsMaxExpansionHeight;
+ protected float mQsExpansionHeight;
+ protected int mQsMinExpansionHeight;
+ protected int mQsMaxExpansionHeight;
private int mQsPeekHeight;
private boolean mStackScrollerOverscrolling;
private boolean mQsExpansionFromOverscroll;
@@ -1072,8 +1072,8 @@
private void setKeyguardBottomAreaVisibility(int statusBarState,
boolean goingToFullShade) {
+ mKeyguardBottomArea.animate().cancel();
if (goingToFullShade) {
- mKeyguardBottomArea.animate().cancel();
mKeyguardBottomArea.animate()
.alpha(0f)
.setStartDelay(mStatusBar.getKeyguardFadingAwayDelay())
@@ -1083,13 +1083,11 @@
.start();
} else if (statusBarState == StatusBarState.KEYGUARD
|| statusBarState == StatusBarState.SHADE_LOCKED) {
- mKeyguardBottomArea.animate().cancel();
if (!mDozing) {
mKeyguardBottomArea.setVisibility(View.VISIBLE);
}
mKeyguardBottomArea.setAlpha(1f);
} else {
- mKeyguardBottomArea.animate().cancel();
mKeyguardBottomArea.setVisibility(View.GONE);
mKeyguardBottomArea.setAlpha(1f);
}
@@ -1196,7 +1194,7 @@
}
}
- private void updateQsExpansion() {
+ protected void updateQsExpansion() {
mQsContainer.setQsExpansion(getQsExpansionFraction(), getHeaderTranslation());
}
@@ -1238,7 +1236,7 @@
}
}
- private void requestScrollerTopPaddingUpdate(boolean animate) {
+ protected void requestScrollerTopPaddingUpdate(boolean animate) {
mNotificationStackScroller.updateTopPadding(calculateQsTopPadding(),
mAnimateNextTopPaddingChange || animate,
mKeyguardShowing
@@ -1520,16 +1518,12 @@
updateQsExpansion();
}
- private float getHeaderTranslation() {
+ protected float getHeaderTranslation() {
if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
return 0;
}
if (mNotificationStackScroller.getNotGoneChildCount() == 0) {
- if (mExpandedHeight / HEADER_RUBBERBAND_FACTOR >= mQsMinExpansionHeight) {
- return 0;
- } else {
- return mExpandedHeight / HEADER_RUBBERBAND_FACTOR - mQsMinExpansionHeight;
- }
+ return Math.min(0, mExpandedHeight / HEADER_RUBBERBAND_FACTOR - mQsMinExpansionHeight);
}
float stackTranslation = mNotificationStackScroller.getStackTranslation();
float translation = stackTranslation / HEADER_RUBBERBAND_FACTOR;
@@ -2191,7 +2185,7 @@
*
* @param x the x-coordinate the touch event
*/
- private void updateVerticalPanelPosition(float x) {
+ protected void updateVerticalPanelPosition(float x) {
if (mNotificationStackScroller.getWidth() * 1.75f > getWidth()) {
resetVerticalPanelPosition();
return;
@@ -2216,7 +2210,7 @@
mQsContainer.setTranslationX(translation);
}
- private void updateStackHeight(float stackHeight) {
+ protected void updateStackHeight(float stackHeight) {
mNotificationStackScroller.setStackHeight(stackHeight);
updateKeyguardBottomAreaAlpha();
}
@@ -2225,7 +2219,7 @@
mBar.panelScrimMinFractionChanged(minFraction);
}
- public void clearNotificattonEffects() {
+ public void clearNotificationEffects() {
mStatusBar.clearNotificationEffects();
}
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..3bb141a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -20,6 +20,7 @@
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;
@@ -30,6 +31,7 @@
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;
@@ -47,7 +49,9 @@
private NextAlarmController mNextAlarmController;
private SettingsButton mSettingsButton;
private View mSettingsContainer;
+
private TextView mAlarmStatus;
+ private TextView mAlarmStatusCollapsed;
private QSPanel mQsPanel;
@@ -56,19 +60,21 @@
private ViewGroup mExpandedGroup;
private ViewGroup mDateTimeGroup;
- private View mEmergencyOnly;
- private TextView mQsDetailHeaderTitle;
+ private ViewGroup mDateTimeAlarmGroup;
+ private TextView mEmergencyOnly;
+
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 mExpansionFraction;
+ private float mDateScaleFactor;
public QuickStatusBarHeader(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -78,11 +84,12 @@
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);
mHeaderQsPanel = (QuickQSPanel) findViewById(R.id.quick_qs_panel);
@@ -91,6 +98,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 +118,29 @@
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);
+
+ 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 - 1;
+ updateDateTimePosition();
}
@Override
@@ -140,15 +171,41 @@
@Override
public void setExpansion(float headerExpansionFraction) {
+ mExpansionFraction = headerExpansionFraction;
+
mExpandedGroup.setAlpha(headerExpansionFraction);
mExpandedGroup.setVisibility(headerExpansionFraction > 0 ? View.VISIBLE : View.INVISIBLE);
+
mHeaderQsPanel.setAlpha(1 - headerExpansionFraction);
mHeaderQsPanel.setVisibility(headerExpansionFraction < 1 ? View.VISIBLE : View.INVISIBLE);
- mDateTimeGroup.setTranslationY(headerExpansionFraction * mDateTimeTranslation);
+ mAlarmStatus.setAlpha(headerExpansionFraction);
+ mAlarmStatusCollapsed.setAlpha(1 - headerExpansionFraction);
+ updateAlarmVisibilities();
+
+ float textScale = headerExpansionFraction * mDateScaleFactor;
+ mDateTimeGroup.setScaleX(1 + textScale);
+ mDateTimeGroup.setScaleY(1 + textScale);
+ mDateTimeGroup.setTranslationX(textScale * mDateTimeGroup.getWidth() / 2);
+ mDateTimeGroup.setTranslationY(textScale * mDateTimeGroup.getHeight() / 2);
+ updateDateTimePosition();
+
mEmergencyOnly.setAlpha(headerExpansionFraction);
}
+ private void updateAlarmVisibilities() {
+ mAlarmStatus.setVisibility(mAlarmShowing && mExpansionFraction > 0
+ ? View.VISIBLE : View.INVISIBLE);
+ mAlarmStatusCollapsed.setVisibility(mAlarmShowing && mExpansionFraction < 1
+ ? View.VISIBLE : View.INVISIBLE);
+ }
+
+ private void updateDateTimePosition() {
+ float translation = mAlarmShowing ? mDateTimeAlarmTranslation
+ : mDateTimeTranslation;
+ mDateTimeAlarmGroup.setTranslationY(mExpansionFraction * translation);
+ }
+
public void setListening(boolean listening) {
if (listening == mListening) {
return;
@@ -160,11 +217,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/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index 3dff37b..3bef19e 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -2346,62 +2346,75 @@
ALOGD("nScriptGroupCreate, con(%p)", (RsContext)con);
}
+ jlong id = 0;
+
+ RsScriptKernelID* kernelsPtr;
jint kernelsLen = _env->GetArrayLength(_kernels);
jlong *jKernelsPtr = _env->GetLongArrayElements(_kernels, nullptr);
+
+ RsScriptKernelID* srcPtr;
+ jint srcLen = _env->GetArrayLength(_src);
+ jlong *jSrcPtr = _env->GetLongArrayElements(_src, nullptr);
+
+ RsScriptKernelID* dstkPtr;
+ jint dstkLen = _env->GetArrayLength(_dstk);
+ jlong *jDstkPtr = _env->GetLongArrayElements(_dstk, nullptr);
+
+ RsScriptKernelID* dstfPtr;
+ jint dstfLen = _env->GetArrayLength(_dstf);
+ jlong *jDstfPtr = _env->GetLongArrayElements(_dstf, nullptr);
+
+ RsType* typesPtr;
+ jint typesLen = _env->GetArrayLength(_types);
+ jlong *jTypesPtr = _env->GetLongArrayElements(_types, nullptr);
+
if (jKernelsPtr == nullptr) {
ALOGE("Failed to get Java array elements: kernels");
- return 0;
+ goto cleanup;
}
- RsScriptKernelID* kernelsPtr = (RsScriptKernelID*) malloc(sizeof(RsScriptKernelID) * kernelsLen);
+ if (jSrcPtr == nullptr) {
+ ALOGE("Failed to get Java array elements: src");
+ goto cleanup;
+ }
+ if (jDstkPtr == nullptr) {
+ ALOGE("Failed to get Java array elements: dstk");
+ goto cleanup;
+ }
+ if (jDstfPtr == nullptr) {
+ ALOGE("Failed to get Java array elements: dstf");
+ goto cleanup;
+ }
+ if (jTypesPtr == nullptr) {
+ ALOGE("Failed to get Java array elements: types");
+ goto cleanup;
+ }
+
+ kernelsPtr = (RsScriptKernelID*) malloc(sizeof(RsScriptKernelID) * kernelsLen);
for(int i = 0; i < kernelsLen; ++i) {
kernelsPtr[i] = (RsScriptKernelID)jKernelsPtr[i];
}
- jint srcLen = _env->GetArrayLength(_src);
- jlong *jSrcPtr = _env->GetLongArrayElements(_src, nullptr);
- if (jSrcPtr == nullptr) {
- ALOGE("Failed to get Java array elements: src");
- return 0;
- }
- RsScriptKernelID* srcPtr = (RsScriptKernelID*) malloc(sizeof(RsScriptKernelID) * srcLen);
+ srcPtr = (RsScriptKernelID*) malloc(sizeof(RsScriptKernelID) * srcLen);
for(int i = 0; i < srcLen; ++i) {
srcPtr[i] = (RsScriptKernelID)jSrcPtr[i];
}
- jint dstkLen = _env->GetArrayLength(_dstk);
- jlong *jDstkPtr = _env->GetLongArrayElements(_dstk, nullptr);
- if (jDstkPtr == nullptr) {
- ALOGE("Failed to get Java array elements: dstk");
- return 0;
- }
- RsScriptKernelID* dstkPtr = (RsScriptKernelID*) malloc(sizeof(RsScriptKernelID) * dstkLen);
+ dstkPtr = (RsScriptKernelID*) malloc(sizeof(RsScriptKernelID) * dstkLen);
for(int i = 0; i < dstkLen; ++i) {
dstkPtr[i] = (RsScriptKernelID)jDstkPtr[i];
}
- jint dstfLen = _env->GetArrayLength(_dstf);
- jlong *jDstfPtr = _env->GetLongArrayElements(_dstf, nullptr);
- if (jDstfPtr == nullptr) {
- ALOGE("Failed to get Java array elements: dstf");
- return 0;
- }
- RsScriptKernelID* dstfPtr = (RsScriptKernelID*) malloc(sizeof(RsScriptKernelID) * dstfLen);
+ dstfPtr = (RsScriptKernelID*) malloc(sizeof(RsScriptKernelID) * dstfLen);
for(int i = 0; i < dstfLen; ++i) {
dstfPtr[i] = (RsScriptKernelID)jDstfPtr[i];
}
- jint typesLen = _env->GetArrayLength(_types);
- jlong *jTypesPtr = _env->GetLongArrayElements(_types, nullptr);
- if (jTypesPtr == nullptr) {
- ALOGE("Failed to get Java array elements: types");
- return 0;
- }
- RsType* typesPtr = (RsType*) malloc(sizeof(RsType) * typesLen);
+ typesPtr = (RsType*) malloc(sizeof(RsType) * typesLen);
for(int i = 0; i < typesLen; ++i) {
typesPtr[i] = (RsType)jTypesPtr[i];
}
- jlong id = (jlong)(uintptr_t)rsScriptGroupCreate((RsContext)con,
+ id = (jlong)(uintptr_t)rsScriptGroupCreate((RsContext)con,
(RsScriptKernelID *)kernelsPtr, kernelsLen * sizeof(RsScriptKernelID),
(RsScriptKernelID *)srcPtr, srcLen * sizeof(RsScriptKernelID),
(RsScriptKernelID *)dstkPtr, dstkLen * sizeof(RsScriptKernelID),
@@ -2413,11 +2426,24 @@
free(dstkPtr);
free(dstfPtr);
free(typesPtr);
- _env->ReleaseLongArrayElements(_kernels, jKernelsPtr, 0);
- _env->ReleaseLongArrayElements(_src, jSrcPtr, 0);
- _env->ReleaseLongArrayElements(_dstk, jDstkPtr, 0);
- _env->ReleaseLongArrayElements(_dstf, jDstfPtr, 0);
- _env->ReleaseLongArrayElements(_types, jTypesPtr, 0);
+
+cleanup:
+ if (jKernelsPtr != nullptr) {
+ _env->ReleaseLongArrayElements(_kernels, jKernelsPtr, 0);
+ }
+ if (jSrcPtr != nullptr) {
+ _env->ReleaseLongArrayElements(_src, jSrcPtr, 0);
+ }
+ if (jDstkPtr != nullptr) {
+ _env->ReleaseLongArrayElements(_dstk, jDstkPtr, 0);
+ }
+ if (jDstfPtr != nullptr) {
+ _env->ReleaseLongArrayElements(_dstf, jDstfPtr, 0);
+ }
+ if (jTypesPtr != nullptr) {
+ _env->ReleaseLongArrayElements(_types, jTypesPtr, 0);
+ }
+
return id;
}
@@ -2662,45 +2688,61 @@
ALOGD("nMeshCreate, con(%p)", (RsContext)con);
}
+ jlong id = 0;
+
+ RsAllocation* vtxPtr;
jint vtxLen = _env->GetArrayLength(_vtx);
jlong *jVtxPtr = _env->GetLongArrayElements(_vtx, nullptr);
+
+ RsAllocation* idxPtr;
+ jint idxLen = _env->GetArrayLength(_idx);
+ jlong *jIdxPtr = _env->GetLongArrayElements(_idx, nullptr);
+
+ jint primLen = _env->GetArrayLength(_prim);
+ jint *primPtr = _env->GetIntArrayElements(_prim, nullptr);
+
if (jVtxPtr == nullptr) {
ALOGE("Failed to get Java array elements: vtx");
- return 0;
+ goto cleanupMesh;
}
- RsAllocation* vtxPtr = (RsAllocation*) malloc(sizeof(RsAllocation) * vtxLen);
+ if (jIdxPtr == nullptr) {
+ ALOGE("Failed to get Java array elements: idx");
+ goto cleanupMesh;
+ }
+ if (primPtr == nullptr) {
+ ALOGE("Failed to get Java array elements: prim");
+ goto cleanupMesh;
+ }
+
+ vtxPtr = (RsAllocation*) malloc(sizeof(RsAllocation) * vtxLen);
for(int i = 0; i < vtxLen; ++i) {
vtxPtr[i] = (RsAllocation)(uintptr_t)jVtxPtr[i];
}
- jint idxLen = _env->GetArrayLength(_idx);
- jlong *jIdxPtr = _env->GetLongArrayElements(_idx, nullptr);
- if (jIdxPtr == nullptr) {
- ALOGE("Failed to get Java array elements: idx");
- return 0;
- }
- RsAllocation* idxPtr = (RsAllocation*) malloc(sizeof(RsAllocation) * idxLen);
+ idxPtr = (RsAllocation*) malloc(sizeof(RsAllocation) * idxLen);
for(int i = 0; i < idxLen; ++i) {
idxPtr[i] = (RsAllocation)(uintptr_t)jIdxPtr[i];
}
- jint primLen = _env->GetArrayLength(_prim);
- jint *primPtr = _env->GetIntArrayElements(_prim, nullptr);
- if (primPtr == nullptr) {
- ALOGE("Failed to get Java array elements: prim");
- return 0;
- }
-
- jlong id = (jlong)(uintptr_t)rsMeshCreate((RsContext)con,
- (RsAllocation *)vtxPtr, vtxLen,
- (RsAllocation *)idxPtr, idxLen,
- (uint32_t *)primPtr, primLen);
+ id = (jlong)(uintptr_t)rsMeshCreate((RsContext)con,
+ (RsAllocation *)vtxPtr, vtxLen,
+ (RsAllocation *)idxPtr, idxLen,
+ (uint32_t *)primPtr, primLen);
free(vtxPtr);
free(idxPtr);
- _env->ReleaseLongArrayElements(_vtx, jVtxPtr, 0);
- _env->ReleaseLongArrayElements(_idx, jIdxPtr, 0);
- _env->ReleaseIntArrayElements(_prim, primPtr, 0);
+
+cleanupMesh:
+ if (jVtxPtr != nullptr) {
+ _env->ReleaseLongArrayElements(_vtx, jVtxPtr, 0);
+ }
+ if (jIdxPtr != nullptr) {
+ _env->ReleaseLongArrayElements(_idx, jIdxPtr, 0);
+ }
+ if (primPtr != nullptr) {
+ _env->ReleaseIntArrayElements(_prim, primPtr, 0);
+ }
+
return id;
}
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..7770d53 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -3004,6 +3004,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/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/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index cca6fc5..1166ae1 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -1397,7 +1397,7 @@
}
intentActivity.deliverNewIntentLocked(mCallingUid, mStartActivity.intent,
mStartActivity.launchedFromPackage);
- } else if (!mStartActivity.intent.filterEquals(intentActivity.task.intent)) {
+ } else if (!mStartActivity.intent.filterEquals(intentActivity.intent)) {
// In this case we are launching the root activity of the task, but with a
// different intent. We should start a new instance on top.
mAddingToTask = true;
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..12c70a3 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,22 @@
importance = IMPORTANCE_DEFAULT;
}
+ try {
+ final ApplicationInfo applicationInfo = mContext.getPackageManager().getApplicationInfo(
+ sbn.getPackageName(), 0);
+ 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/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index c6613f5..8d75f60 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -346,8 +346,7 @@
String packageName = component.getPackageName();
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", packageName, null));
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK |
- Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.setSourceBounds(sourceBounds);
mContext.startActivityAsUser(intent, opts, user);
} finally {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 5db7e63..cc5b80e 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;
@@ -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/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 412a455..685403c 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) {
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..d169b34 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -919,6 +919,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 +946,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 +978,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/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c8f5dda..00a3e2e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8839,7 +8839,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
@@ -10489,6 +10490,11 @@
continue;
}
+ child.getVisibleBounds(mTmpRect);
+ if (!mTmpRect.contains(x, y)) {
+ continue;
+ }
+
child.getTouchableRegion(mTmpRegion);
final int touchFlags = flags &
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..79d2307 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 {
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 46ad8a1..acc752a 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,31 +399,38 @@
}
}
- /** 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);
+ 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);
synchronized (mLock) {
final int packageCount = packages.size();
for (int p = 0; p < packageCount; p++) {
@@ -439,6 +447,11 @@
}
}
}
+ 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/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/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}