Merge "Fix API level in documentation to refer to MNC"
diff --git a/api/system-current.txt b/api/system-current.txt
index 16ffc31..137bb19 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -41589,10 +41589,19 @@
ctor public WebViewFactory();
method public static android.content.pm.PackageInfo getLoadedPackageInfo();
method public static java.lang.String getWebViewPackageName();
+ method public static int loadWebViewNativeLibraryFromPackage(java.lang.String);
method public static void onWebViewUpdateInstalled();
method public static void prepareWebViewInSystemServer();
method public static void prepareWebViewInZygote();
field public static final java.lang.String CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY = "persist.sys.webview.vmsize";
+ field public static final int LIBLOAD_ADDRESS_SPACE_NOT_RESERVED = 2; // 0x2
+ field public static final int LIBLOAD_FAILED_JNI_CALL = 7; // 0x7
+ field public static final int LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES = 4; // 0x4
+ field public static final int LIBLOAD_FAILED_TO_LOAD_LIBRARY = 6; // 0x6
+ field public static final int LIBLOAD_FAILED_TO_OPEN_RELRO_FILE = 5; // 0x5
+ field public static final int LIBLOAD_FAILED_WAITING_FOR_RELRO = 3; // 0x3
+ field public static final int LIBLOAD_SUCCESS = 0; // 0x0
+ field public static final int LIBLOAD_WRONG_PACKAGE_NAME = 1; // 0x1
}
public abstract interface WebViewFactoryProvider {
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 56cd53e..ebb3c43 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -16,6 +16,8 @@
package android.app;
+import android.Manifest;
+import android.annotation.RequiresPermission;
import android.app.trust.ITrustManager;
import android.content.Context;
import android.content.Intent;
@@ -111,6 +113,7 @@
*
* @see #reenableKeyguard()
*/
+ @RequiresPermission(Manifest.permission.DISABLE_KEYGUARD)
public void disableKeyguard() {
try {
mWM.disableKeyguard(mToken, mTag);
@@ -132,6 +135,7 @@
*
* @see #disableKeyguard()
*/
+ @RequiresPermission(Manifest.permission.DISABLE_KEYGUARD)
public void reenableKeyguard() {
try {
mWM.reenableKeyguard(mToken);
@@ -302,6 +306,7 @@
* once the user has gotten past the keyguard.
*/
@Deprecated
+ @RequiresPermission(Manifest.permission.DISABLE_KEYGUARD)
public void exitKeyguardSecurely(final OnKeyguardExitResult callback) {
try {
mWM.exitKeyguardSecurely(new IOnKeyguardExitResult.Stub() {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index a20aa668..47133d4 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2651,14 +2651,12 @@
/**
* @hide
- * Sets the given package as the device owner. The package must already be installed and there
- * shouldn't be an existing device owner registered, for this call to succeed. Also, this
- * method must be called before the device is provisioned.
+ * Sets the given package as the device owner.
+ * Same as {@link #setDeviceOwner(String, String)} but without setting a device owner name.
* @param packageName the package name of the application to be registered as the device owner.
* @return whether the package was successfully registered as the device owner.
* @throws IllegalArgumentException if the package name is null or invalid
- * @throws IllegalStateException if a device owner is already registered or the device has
- * already been provisioned.
+ * @throws IllegalStateException If the preconditions mentioned are not met.
*/
public boolean setDeviceOwner(String packageName) throws IllegalArgumentException,
IllegalStateException {
@@ -2667,15 +2665,17 @@
/**
* @hide
- * Sets the given package as the device owner. The package must already be installed and there
- * shouldn't be an existing device owner registered, for this call to succeed. Also, this
- * method must be called before the device is provisioned.
+ * Sets the given package as the device owner. The package must already be installed. There
+ * must not already be a device owner.
+ * Only apps with the MANAGE_PROFILE_AND_DEVICE_OWNERS permission and the shell uid can call
+ * this method.
+ * Calling this after the setup phase of the primary user has completed is allowed only if
+ * the caller is the shell uid, and there are no additional users and no accounts.
* @param packageName the package name of the application to be registered as the device owner.
* @param ownerName the human readable name of the institution that owns this device.
* @return whether the package was successfully registered as the device owner.
* @throws IllegalArgumentException if the package name is null or invalid
- * @throws IllegalStateException if a device owner is already registered or the device has
- * already been provisioned.
+ * @throws IllegalStateException If the preconditions mentioned are not met.
*/
public boolean setDeviceOwner(String packageName, String ownerName)
throws IllegalArgumentException, IllegalStateException {
@@ -2961,14 +2961,18 @@
/**
* @hide
* Sets the given component as the profile owner of the given user profile. The package must
- * already be installed and there shouldn't be an existing profile owner registered for this
- * user. Only the system can call this API if the user has already completed setup.
+ * already be installed. There must not already be a profile owner for this user.
+ * Only apps with the MANAGE_PROFILE_AND_DEVICE_OWNERS permission and the shell uid can call
+ * this method.
+ * Calling this after the setup phase of the specified user has completed is allowed only if:
+ * - the caller is SYSTEM_UID.
+ * - or the caller is the shell uid, and there are no accounts on the specified user.
* @param admin the component name to be registered as profile owner.
* @param ownerName the human readable name of the organisation associated with this DPM.
* @param userHandle the userId to set the profile owner for.
* @return whether the component was successfully registered as the profile owner.
- * @throws IllegalArgumentException if admin is null, the package isn't installed, or
- * the user has already been set up.
+ * @throws IllegalArgumentException if admin is null, the package isn't installed, or the
+ * preconditions mentioned are not met.
*/
public boolean setProfileOwner(ComponentName admin, String ownerName, int userHandle)
throws IllegalArgumentException {
diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java
index 31e6e25..0cf8df1 100644
--- a/core/java/android/hardware/camera2/CameraCaptureSession.java
+++ b/core/java/android/hardware/camera2/CameraCaptureSession.java
@@ -65,6 +65,12 @@
public abstract class CameraCaptureSession implements AutoCloseable {
/**
+ * Used to identify invalid session ID.
+ * @hide
+ */
+ public static final int SESSION_ID_NONE = -1;
+
+ /**
* Get the camera device that this session is created for.
*/
public abstract CameraDevice getDevice();
@@ -168,10 +174,11 @@
* @throws IllegalArgumentException if the request targets no Surfaces or Surfaces that are not
* configured as outputs for this session; or a reprocess
* capture request is submitted in a non-reprocessible capture
- * session; or the capture targets a Surface in the middle
- * of being {@link #prepare prepared}; or the handler is
- * null, the listener is not null, and the calling thread has
- * no looper.
+ * session; or the reprocess capture request was created with
+ * a {@link TotalCaptureResult} from a different session; or
+ * the capture targets a Surface in the middle of being
+ * {@link #prepare prepared}; or the handler is null, the
+ * listener is not null, and the calling thread has no looper.
*
* @see #captureBurst
* @see #setRepeatingRequest
@@ -226,7 +233,9 @@
* capture request is submitted in a non-reprocessible capture
* session; or the list of requests contains both requests to
* capture images from the camera and reprocess capture
- * requests; or one of the captures targets a Surface in the
+ * requests; or one of the reprocess capture requests was
+ * created with a {@link TotalCaptureResult} from a different
+ * session; or one of the captures targets a Surface in the
* middle of being {@link #prepare prepared}; or if the handler
* is null, the listener is not null, and the calling thread
* has no looper.
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 35727e8..19d17b1 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -158,6 +158,9 @@
private final HashSet<Surface> mSurfaceSet;
private final CameraMetadataNative mSettings;
private boolean mIsReprocess;
+ // Each reprocess request must be tied to a reprocessible session ID.
+ // Valid only for reprocess requests (mIsReprocess == true).
+ private int mReprocessibleSessionId;
private Object mUserTag;
@@ -170,6 +173,7 @@
mSettings = new CameraMetadataNative();
mSurfaceSet = new HashSet<Surface>();
mIsReprocess = false;
+ mReprocessibleSessionId = CameraCaptureSession.SESSION_ID_NONE;
}
/**
@@ -182,6 +186,7 @@
mSettings = new CameraMetadataNative(source.mSettings);
mSurfaceSet = (HashSet<Surface>) source.mSurfaceSet.clone();
mIsReprocess = source.mIsReprocess;
+ mReprocessibleSessionId = source.mReprocessibleSessionId;
mUserTag = source.mUserTag;
}
@@ -189,11 +194,36 @@
* Take ownership of passed-in settings.
*
* Used by the Builder to create a mutable CaptureRequest.
+ *
+ * @param settings Settings for this capture request.
+ * @param isReprocess Indicates whether to create a reprocess capture request. {@code true}
+ * to create a reprocess capture request. {@code false} to create a regular
+ * capture request.
+ * @param reprocessibleSessionId The ID of the camera capture session this capture is created
+ * for. This is used to validate if the application submits a
+ * reprocess capture request to the same session where
+ * the {@link TotalCaptureResult}, used to create the reprocess
+ * capture, came from.
+ *
+ * @throws IllegalArgumentException If creating a reprocess capture request with an invalid
+ * reprocessibleSessionId.
+ *
+ * @see CameraDevice#createReprocessCaptureRequest
*/
- private CaptureRequest(CameraMetadataNative settings, boolean isReprocess) {
+ private CaptureRequest(CameraMetadataNative settings, boolean isReprocess,
+ int reprocessibleSessionId) {
mSettings = CameraMetadataNative.move(settings);
mSurfaceSet = new HashSet<Surface>();
mIsReprocess = isReprocess;
+ if (isReprocess) {
+ if (reprocessibleSessionId == CameraCaptureSession.SESSION_ID_NONE) {
+ throw new IllegalArgumentException("Create a reprocess capture request with an " +
+ "invalid session ID: " + reprocessibleSessionId);
+ }
+ mReprocessibleSessionId = reprocessibleSessionId;
+ } else {
+ mReprocessibleSessionId = CameraCaptureSession.SESSION_ID_NONE;
+ }
}
/**
@@ -277,6 +307,23 @@
}
/**
+ * Get the reprocessible session ID this reprocess capture request is associated with.
+ *
+ * @return the reprocessible session ID this reprocess capture request is associated with
+ *
+ * @throws IllegalStateException if this capture request is not a reprocess capture request.
+ * @hide
+ */
+ public int getReprocessibleSessionId() {
+ if (mIsReprocess == false ||
+ mReprocessibleSessionId == CameraCaptureSession.SESSION_ID_NONE) {
+ throw new IllegalStateException("Getting the reprocessible session ID for a "+
+ "non-reprocess capture request is illegal.");
+ }
+ return mReprocessibleSessionId;
+ }
+
+ /**
* Determine whether this CaptureRequest is equal to another CaptureRequest.
*
* <p>A request is considered equal to another is if it's set of key/values is equal, it's
@@ -298,7 +345,8 @@
&& Objects.equals(mUserTag, other.mUserTag)
&& mSurfaceSet.equals(other.mSurfaceSet)
&& mSettings.equals(other.mSettings)
- && mIsReprocess == other.mIsReprocess;
+ && mIsReprocess == other.mIsReprocess
+ && mReprocessibleSessionId == other.mReprocessibleSessionId;
}
@Override
@@ -347,6 +395,7 @@
}
mIsReprocess = (in.readInt() == 0) ? false : true;
+ mReprocessibleSessionId = CameraCaptureSession.SESSION_ID_NONE;
}
@Override
@@ -397,10 +446,23 @@
* Initialize the builder using the template; the request takes
* ownership of the template.
*
+ * @param template Template settings for this capture request.
+ * @param reprocess Indicates whether to create a reprocess capture request. {@code true}
+ * to create a reprocess capture request. {@code false} to create a regular
+ * capture request.
+ * @param reprocessibleSessionId The ID of the camera capture session this capture is
+ * created for. This is used to validate if the application
+ * submits a reprocess capture request to the same session
+ * where the {@link TotalCaptureResult}, used to create the
+ * reprocess capture, came from.
+ *
+ * @throws IllegalArgumentException If creating a reprocess capture request with an invalid
+ * reprocessibleSessionId.
* @hide
*/
- public Builder(CameraMetadataNative template, boolean reprocess) {
- mRequest = new CaptureRequest(template, reprocess);
+ public Builder(CameraMetadataNative template, boolean reprocess,
+ int reprocessibleSessionId) {
+ mRequest = new CaptureRequest(template, reprocess, reprocessibleSessionId);
}
/**
diff --git a/core/java/android/hardware/camera2/TotalCaptureResult.java b/core/java/android/hardware/camera2/TotalCaptureResult.java
index 6f7dd78..fb3c098 100644
--- a/core/java/android/hardware/camera2/TotalCaptureResult.java
+++ b/core/java/android/hardware/camera2/TotalCaptureResult.java
@@ -50,6 +50,7 @@
public final class TotalCaptureResult extends CaptureResult {
private final List<CaptureResult> mPartialResults;
+ private final int mSessionId;
/**
* Takes ownership of the passed-in camera metadata and the partial results
@@ -58,7 +59,7 @@
* @hide
*/
public TotalCaptureResult(CameraMetadataNative results, CaptureRequest parent,
- CaptureResultExtras extras, List<CaptureResult> partials) {
+ CaptureResultExtras extras, List<CaptureResult> partials, int sessionId) {
super(results, parent, extras);
if (partials == null) {
@@ -66,6 +67,8 @@
} else {
mPartialResults = partials;
}
+
+ mSessionId = sessionId;
}
/**
@@ -78,6 +81,7 @@
super(results, sequenceId);
mPartialResults = new ArrayList<>();
+ mSessionId = CameraCaptureSession.SESSION_ID_NONE;
}
/**
@@ -95,4 +99,14 @@
public List<CaptureResult> getPartialResults() {
return Collections.unmodifiableList(mPartialResults);
}
+
+ /**
+ * Get the ID of the session where the capture request of this result was submitted.
+ *
+ * @return The session ID
+ * @hide
+ */
+ public int getSessionId() {
+ return mSessionId;
+ }
}
diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
index c74204d..3c19529 100644
--- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
@@ -156,9 +156,10 @@
} else if (request.isReprocess() && !isReprocessible()) {
throw new IllegalArgumentException("this capture session cannot handle reprocess " +
"requests");
+ } else if (request.isReprocess() && request.getReprocessibleSessionId() != mId) {
+ throw new IllegalArgumentException("capture request was created for another session");
}
-
checkNotClosed();
handler = checkHandler(handler, callback);
@@ -185,12 +186,17 @@
if (reprocess && !isReprocessible()) {
throw new IllegalArgumentException("this capture session cannot handle reprocess " +
"requests");
+ } else if (reprocess && requests.get(0).getReprocessibleSessionId() != mId) {
+ throw new IllegalArgumentException("capture request was created for another session");
}
for (int i = 1; i < requests.size(); i++) {
if (requests.get(i).isReprocess() != reprocess) {
throw new IllegalArgumentException("cannot mix regular and reprocess capture " +
" requests");
+ } else if (reprocess && requests.get(i).getReprocessibleSessionId() != mId) {
+ throw new IllegalArgumentException("capture request was created for another " +
+ "session");
}
}
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 1e680dfd..ff4ad79 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -585,8 +585,8 @@
return null;
}
- CaptureRequest.Builder builder =
- new CaptureRequest.Builder(templatedRequest, /*reprocess*/false);
+ CaptureRequest.Builder builder = new CaptureRequest.Builder(
+ templatedRequest, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE);
return builder;
}
@@ -601,7 +601,8 @@
CameraMetadataNative resultMetadata = new
CameraMetadataNative(inputResult.getNativeCopy());
- return new CaptureRequest.Builder(resultMetadata, /*reprocess*/true);
+ return new CaptureRequest.Builder(resultMetadata, /*reprocess*/true,
+ inputResult.getSessionId());
}
}
@@ -763,7 +764,7 @@
if (callback != null) {
mCaptureCallbackMap.put(requestId, new CaptureCallbackHolder(callback,
- requestList, handler, repeating));
+ requestList, handler, repeating, mNextSessionId - 1));
} else {
if (DEBUG) {
Log.d(TAG, "Listen for request " + requestId + " is null");
@@ -1095,9 +1096,10 @@
private final CaptureCallback mCallback;
private final List<CaptureRequest> mRequestList;
private final Handler mHandler;
+ private final int mSessionId;
CaptureCallbackHolder(CaptureCallback callback, List<CaptureRequest> requestList,
- Handler handler, boolean repeating) {
+ Handler handler, boolean repeating, int sessionId) {
if (callback == null || handler == null) {
throw new UnsupportedOperationException(
"Must have a valid handler and a valid callback");
@@ -1106,6 +1108,7 @@
mHandler = handler;
mRequestList = new ArrayList<CaptureRequest>(requestList);
mCallback = callback;
+ mSessionId = sessionId;
}
public boolean isRepeating() {
@@ -1140,6 +1143,10 @@
return mHandler;
}
+ public int getSessionId() {
+ return mSessionId;
+ }
+
}
/**
@@ -1643,8 +1650,8 @@
List<CaptureResult> partialResults =
mFrameNumberTracker.popPartialResults(frameNumber);
- final TotalCaptureResult resultAsCapture =
- new TotalCaptureResult(result, request, resultExtras, partialResults);
+ final TotalCaptureResult resultAsCapture = new TotalCaptureResult(result,
+ request, resultExtras, partialResults, holder.getSessionId());
// Final capture result
resultDispatch = new Runnable() {
@@ -1665,7 +1672,8 @@
holder.getHandler().post(resultDispatch);
// Collect the partials for a total result; or mark the frame as totally completed
- mFrameNumberTracker.updateTracker(frameNumber, finalResult, isPartialResult, isReprocess);
+ mFrameNumberTracker.updateTracker(frameNumber, finalResult, isPartialResult,
+ isReprocess);
// Fire onCaptureSequenceCompleted
if (!isPartialResult) {
diff --git a/core/java/android/provider/Browser.java b/core/java/android/provider/Browser.java
index 69a05c4..bae06b8 100644
--- a/core/java/android/provider/Browser.java
+++ b/core/java/android/provider/Browser.java
@@ -16,6 +16,7 @@
package android.provider;
+import android.annotation.RequiresPermission;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
@@ -32,6 +33,9 @@
import android.util.Log;
import android.webkit.WebIconDatabase;
+import static android.Manifest.permission.READ_HISTORY_BOOKMARKS;
+import static android.Manifest.permission.WRITE_HISTORY_BOOKMARKS;
+
public class Browser {
private static final String LOGTAG = "browser";
@@ -41,6 +45,8 @@
* {@link android.Manifest.permission#READ_HISTORY_BOOKMARKS} permission and writing to it
* requires the {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS} permission.
*/
+ @RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS))
+ @RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS))
public static final Uri BOOKMARKS_URI = Uri.parse("content://browser/bookmarks");
/**
@@ -122,6 +128,8 @@
* {@link android.Manifest.permission#READ_HISTORY_BOOKMARKS} permission and writing to it
* requires the {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS} permission.
*/
+ @RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS))
+ @RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS))
public static final Uri SEARCHES_URI = Uri.parse("content://browser/searches");
/**
@@ -233,6 +241,7 @@
*
* @param cr The ContentResolver used to access the database.
*/
+ @RequiresPermission(READ_HISTORY_BOOKMARKS)
public static final Cursor getAllBookmarks(ContentResolver cr) throws
IllegalStateException {
return cr.query(Bookmarks.CONTENT_URI,
@@ -248,6 +257,7 @@
*
* @param cr The ContentResolver used to access the database.
*/
+ @RequiresPermission(READ_HISTORY_BOOKMARKS)
public static final Cursor getAllVisitedUrls(ContentResolver cr) throws
IllegalStateException {
return cr.query(Combined.CONTENT_URI,
@@ -308,6 +318,7 @@
* @param real If true, this is an actual visit, and should add to the
* number of visits. If false, the user entered it manually.
*/
+ @RequiresPermission(allOf = {READ_HISTORY_BOOKMARKS, WRITE_HISTORY_BOOKMARKS})
public static final void updateVisitedHistory(ContentResolver cr,
String url, boolean real) {
long now = System.currentTimeMillis();
@@ -358,6 +369,7 @@
* @param cr The ContentResolver used to access the database.
* @hide pending API council approval
*/
+ @RequiresPermission(READ_HISTORY_BOOKMARKS)
public static final String[] getVisitedHistory(ContentResolver cr) {
Cursor c = null;
String[] str = null;
@@ -393,6 +405,7 @@
*
* @param cr The ContentResolver used to access the database.
*/
+ @RequiresPermission(allOf = {READ_HISTORY_BOOKMARKS, WRITE_HISTORY_BOOKMARKS})
public static final void truncateHistory(ContentResolver cr) {
// TODO make a single request to the provider to do this in a single transaction
Cursor cursor = null;
@@ -424,6 +437,7 @@
* @param cr The ContentResolver used to access the database.
* @return boolean True if the history can be cleared.
*/
+ @RequiresPermission(READ_HISTORY_BOOKMARKS)
public static final boolean canClearHistory(ContentResolver cr) {
Cursor cursor = null;
boolean ret = false;
@@ -446,6 +460,7 @@
* Requires {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS}
* @param cr The ContentResolver used to access the database.
*/
+ @RequiresPermission(WRITE_HISTORY_BOOKMARKS)
public static final void clearHistory(ContentResolver cr) {
deleteHistoryWhere(cr, null);
}
@@ -461,6 +476,7 @@
* @param whereClause String to limit the items affected.
* null means all items.
*/
+ @RequiresPermission(allOf = {READ_HISTORY_BOOKMARKS, WRITE_HISTORY_BOOKMARKS})
private static final void deleteHistoryWhere(ContentResolver cr, String whereClause) {
Cursor cursor = null;
try {
@@ -486,6 +502,7 @@
* @param end Last date to remove. If -1, all dates after begin.
* Non-inclusive.
*/
+ @RequiresPermission(WRITE_HISTORY_BOOKMARKS)
public static final void deleteHistoryTimeFrame(ContentResolver cr,
long begin, long end) {
String whereClause;
@@ -511,6 +528,7 @@
* @param cr The ContentResolver used to access the database.
* @param url url to remove.
*/
+ @RequiresPermission(WRITE_HISTORY_BOOKMARKS)
public static final void deleteFromHistory(ContentResolver cr,
String url) {
cr.delete(History.CONTENT_URI, History.URL + "=?", new String[] { url });
@@ -523,6 +541,7 @@
* @param cr The ContentResolver used to access the database.
* @param search The string to add to the searches database.
*/
+ @RequiresPermission(allOf = {READ_HISTORY_BOOKMARKS, WRITE_HISTORY_BOOKMARKS})
public static final void addSearchUrl(ContentResolver cr, String search) {
// The content provider will take care of updating existing searches instead of duplicating
ContentValues values = new ContentValues();
@@ -536,6 +555,7 @@
* Requires {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS}
* @param cr The ContentResolver used to access the database.
*/
+ @RequiresPermission(WRITE_HISTORY_BOOKMARKS)
public static final void clearSearches(ContentResolver cr) {
// FIXME: Should this clear the urls to which these searches lead?
// (i.e. remove google.com/query= blah blah blah)
@@ -557,6 +577,7 @@
* @param listener IconListener that gets the icons once they are
* retrieved.
*/
+ @RequiresPermission(READ_HISTORY_BOOKMARKS)
public static final void requestAllIcons(ContentResolver cr, String where,
WebIconDatabase.IconListener listener) {
// Do nothing: this is no longer used.
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 3340c73..9782d72 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -76,6 +76,18 @@
private static boolean sAddressSpaceReserved = false;
private static PackageInfo sPackageInfo;
+ // Error codes for loadWebViewNativeLibraryFromPackage
+ public static final int LIBLOAD_SUCCESS = 0;
+ public static final int LIBLOAD_WRONG_PACKAGE_NAME = 1;
+ public static final int LIBLOAD_ADDRESS_SPACE_NOT_RESERVED = 2;
+ public static final int LIBLOAD_FAILED_WAITING_FOR_RELRO = 3;
+ public static final int LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES = 4;
+
+ // native relro loading error codes
+ public static final int LIBLOAD_FAILED_TO_OPEN_RELRO_FILE = 5;
+ public static final int LIBLOAD_FAILED_TO_LOAD_LIBRARY = 6;
+ public static final int LIBLOAD_FAILED_JNI_CALL = 7;
+
private static class MissingWebViewPackageException extends AndroidRuntimeException {
public MissingWebViewPackageException(String message) { super(message); }
public MissingWebViewPackageException(Exception e) { super(e); }
@@ -136,6 +148,18 @@
return sPackageInfo;
}
+ /**
+ * Load the native library for the given package name iff that package
+ * name is the same as the one providing the current webview.
+ */
+ public static int loadWebViewNativeLibraryFromPackage(String packageName) {
+ sPackageInfo = findPreferredWebViewPackage();
+ if (packageName != null && packageName.equals(sPackageInfo.packageName)) {
+ return loadNativeLibrary();
+ }
+ return LIBLOAD_WRONG_PACKAGE_NAME;
+ }
+
static WebViewFactoryProvider getProvider() {
synchronized (sProviderLock) {
// For now the main purpose of this function (and the factory abstraction) is to keep
@@ -434,32 +458,34 @@
}
}
- private static void loadNativeLibrary() {
+ private static int loadNativeLibrary() {
if (!sAddressSpaceReserved) {
Log.e(LOGTAG, "can't load with relro file; address space not reserved");
- return;
+ return LIBLOAD_ADDRESS_SPACE_NOT_RESERVED;
}
try {
getUpdateService().waitForRelroCreationCompleted(VMRuntime.getRuntime().is64Bit());
} catch (RemoteException e) {
Log.e(LOGTAG, "error waiting for relro creation, proceeding without", e);
- return;
+ return LIBLOAD_FAILED_WAITING_FOR_RELRO;
}
try {
String[] args = getWebViewNativeLibraryPaths();
- boolean result = nativeLoadWithRelroFile(args[0] /* path32 */,
+ int result = nativeLoadWithRelroFile(args[0] /* path32 */,
args[1] /* path64 */,
CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
CHROMIUM_WEBVIEW_NATIVE_RELRO_64);
- if (!result) {
+ if (result != LIBLOAD_SUCCESS) {
Log.w(LOGTAG, "failed to load with relro file, proceeding without");
} else if (DEBUG) {
Log.v(LOGTAG, "loaded with relro file");
}
+ return result;
} catch (MissingWebViewPackageException e) {
Log.e(LOGTAG, "Failed to list WebView package libraries for loadNativeLibrary", e);
+ return LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES;
}
}
@@ -470,6 +496,6 @@
private static native boolean nativeReserveAddressSpace(long addressSpaceToReserve);
private static native boolean nativeCreateRelroFile(String lib32, String lib64,
String relro32, String relro64);
- private static native boolean nativeLoadWithRelroFile(String lib32, String lib64,
+ private static native int nativeLoadWithRelroFile(String lib32, String lib64,
String relro32, String relro64);
}
diff --git a/core/java/android/widget/DayPickerView.java b/core/java/android/widget/DayPickerView.java
index c6b4d7e..113e597 100644
--- a/core/java/android/widget/DayPickerView.java
+++ b/core/java/android/widget/DayPickerView.java
@@ -196,9 +196,23 @@
}
@Override
+ public void onRtlPropertiesChanged(@ResolvedLayoutDir int layoutDirection) {
+ super.onRtlPropertiesChanged(layoutDirection);
+
+ requestLayout();
+ }
+
+ @Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- final ImageButton leftButton = mPrevButton;
- final ImageButton rightButton = mNextButton;
+ final ImageButton leftButton;
+ final ImageButton rightButton;
+ if (isLayoutRtl()) {
+ leftButton = mNextButton;
+ rightButton = mPrevButton;
+ } else {
+ leftButton = mPrevButton;
+ rightButton = mNextButton;
+ }
final int width = right - left;
final int height = bottom - top;
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index b049e49..652fff2 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -1957,6 +1957,9 @@
if (mPositionListener != null) {
mPositionListener.onScrollChanged();
}
+ if (mSelectionActionMode != null) {
+ mSelectionActionMode.invalidateContentRect();
+ }
}
/**
@@ -3085,10 +3088,12 @@
MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
}
- menu.add(0, TextView.ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
- setAlphabeticShortcut('a').
- setShowAsAction(
- MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+ if (canSelectText() && !hasPasswordTransformationMethod()) {
+ menu.add(0, TextView.ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
+ setAlphabeticShortcut('a').
+ setShowAsAction(
+ MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+ }
updateReplaceItem(menu);
}
diff --git a/core/java/android/widget/SimpleMonthView.java b/core/java/android/widget/SimpleMonthView.java
index 0249c22..2778f0f 100644
--- a/core/java/android/widget/SimpleMonthView.java
+++ b/core/java/android/widget/SimpleMonthView.java
@@ -162,7 +162,6 @@
mTitleFormatter = new SimpleDateFormat(titleFormat, locale);
mDayOfWeekFormatter = new SimpleDateFormat(DAY_OF_WEEK_FORMAT, locale);
- setClickable(true);
initPaints(res);
}
@@ -318,7 +317,8 @@
final int x = (int) (event.getX() + 0.5f);
final int y = (int) (event.getY() + 0.5f);
- switch (event.getAction()) {
+ final int action = event.getAction();
+ switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
final int touchedItem = getDayAtLocation(x, y);
@@ -326,6 +326,10 @@
mTouchedItem = touchedItem;
invalidate();
}
+ if (action == MotionEvent.ACTION_DOWN && touchedItem < 0) {
+ // Touch something that's not an item, reject event.
+ return false;
+ }
break;
case MotionEvent.ACTION_UP:
@@ -376,9 +380,16 @@
for (int col = 0; col < DAYS_IN_WEEK; col++) {
final int colCenter = colWidth * col + colWidth / 2;
+ final int colCenterRtl;
+ if (isLayoutRtl()) {
+ colCenterRtl = mPaddedWidth - colCenter;
+ } else {
+ colCenterRtl = colCenter;
+ }
+
final int dayOfWeek = (col + mWeekStart) % DAYS_IN_WEEK;
final String label = getDayOfWeekLabel(dayOfWeek);
- canvas.drawText(label, colCenter, rowCenter - halfLineHeight, p);
+ canvas.drawText(label, colCenterRtl, rowCenter - halfLineHeight, p);
}
}
@@ -402,6 +413,13 @@
for (int day = 1, col = findDayOffset(); day <= mDaysInMonth; day++) {
final int colCenter = colWidth * col + colWidth / 2;
+ final int colCenterRtl;
+ if (isLayoutRtl()) {
+ colCenterRtl = mPaddedWidth - colCenter;
+ } else {
+ colCenterRtl = colCenter;
+ }
+
int stateMask = 0;
if (day >= mEnabledDayStart && day <= mEnabledDayEnd) {
@@ -413,12 +431,12 @@
stateMask |= StateSet.VIEW_STATE_ACTIVATED;
// Adjust the circle to be centered on the row.
- canvas.drawCircle(colCenter, rowCenter, mDaySelectorRadius, mDaySelectorPaint);
+ canvas.drawCircle(colCenterRtl, rowCenter, mDaySelectorRadius, mDaySelectorPaint);
} else if (mTouchedItem == day) {
stateMask |= StateSet.VIEW_STATE_PRESSED;
// Adjust the circle to be centered on the row.
- canvas.drawCircle(colCenter, rowCenter, mDaySelectorRadius, mDayHighlightPaint);
+ canvas.drawCircle(colCenterRtl, rowCenter, mDaySelectorRadius, mDayHighlightPaint);
}
final boolean isDayToday = mToday == day;
@@ -431,7 +449,7 @@
}
p.setColor(dayTextColor);
- canvas.drawText(Integer.toString(day), colCenter, rowCenter - halfLineHeight, p);
+ canvas.drawText(Integer.toString(day), colCenterRtl, rowCenter - halfLineHeight, p);
col++;
@@ -583,6 +601,13 @@
}
@Override
+ public void onRtlPropertiesChanged(@ResolvedLayoutDir int layoutDirection) {
+ super.onRtlPropertiesChanged(layoutDirection);
+
+ requestLayout();
+ }
+
+ @Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (!changed) {
return;
@@ -657,8 +682,16 @@
return -1;
}
+ // Adjust for RTL after applying padding.
+ final int paddedXRtl;
+ if (isLayoutRtl()) {
+ paddedXRtl = mPaddedWidth - paddedX;
+ } else {
+ paddedXRtl = paddedX;
+ }
+
final int row = (paddedY - headerHeight) / mDayHeight;
- final int col = (paddedX * DAYS_IN_WEEK) / mPaddedWidth;
+ final int col = (paddedXRtl * DAYS_IN_WEEK) / mPaddedWidth;
final int index = col + row * DAYS_IN_WEEK;
final int day = index + 1 - findDayOffset();
if (day < 1 || day > mDaysInMonth) {
@@ -681,10 +714,15 @@
final int index = id - 1 + findDayOffset();
- // Compute left edge.
+ // Compute left edge, taking into account RTL.
final int col = index % DAYS_IN_WEEK;
final int colWidth = mCellWidth;
- final int left = getPaddingLeft() + col * colWidth;
+ final int left;
+ if (isLayoutRtl()) {
+ left = getWidth() - getPaddingRight() - (col + 1) * colWidth;
+ } else {
+ left = getPaddingLeft() + col * colWidth;
+ }
// Compute top edge.
final int row = index / DAYS_IN_WEEK;
diff --git a/core/java/com/android/internal/widget/SwipeDismissLayout.java b/core/java/com/android/internal/widget/SwipeDismissLayout.java
index 89990c2..191662c 100644
--- a/core/java/com/android/internal/widget/SwipeDismissLayout.java
+++ b/core/java/com/android/internal/widget/SwipeDismissLayout.java
@@ -111,7 +111,7 @@
}
private void init(Context context) {
- ViewConfiguration vc = ViewConfiguration.get(getContext());
+ ViewConfiguration vc = ViewConfiguration.get(context);
mSlop = vc.getScaledTouchSlop();
mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
@@ -290,7 +290,7 @@
float deltaX = ev.getRawX() - mDownX;
float deltaY = ev.getRawY() - mDownY;
if ((deltaX * deltaX) + (deltaY * deltaY) > mSlop * mSlop) {
- mSwiping = deltaX > mSlop * 2 && Math.abs(deltaY) < mSlop * 2;
+ mSwiping = deltaX > mSlop * 2 && Math.abs(deltaY) < Math.abs(deltaX);
} else {
mSwiping = false;
}
@@ -299,9 +299,9 @@
private void updateDismiss(MotionEvent ev) {
float deltaX = ev.getRawX() - mDownX;
+ mVelocityTracker.addMovement(ev);
+ mVelocityTracker.computeCurrentVelocity(1000);
if (!mDismissed) {
- mVelocityTracker.addMovement(ev);
- mVelocityTracker.computeCurrentVelocity(1000);
if (deltaX > (getWidth() * DISMISS_MIN_DRAG_WIDTH_RATIO) &&
ev.getRawX() >= mLastX) {
@@ -311,7 +311,9 @@
// Check if the user tried to undo this.
if (mDismissed && mSwiping) {
// Check if the user's finger is actually back
- if (deltaX < (getWidth() * DISMISS_MIN_DRAG_WIDTH_RATIO)) {
+ if (deltaX < (getWidth() * DISMISS_MIN_DRAG_WIDTH_RATIO) ||
+ // or user is flinging back left
+ mVelocityTracker.getXVelocity() < -mMinFlingVelocity) {
mDismissed = false;
}
}
diff --git a/core/java/com/android/internal/widget/ViewPager.java b/core/java/com/android/internal/widget/ViewPager.java
index 5c08daf..441e640 100644
--- a/core/java/com/android/internal/widget/ViewPager.java
+++ b/core/java/com/android/internal/widget/ViewPager.java
@@ -27,9 +27,9 @@
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
-import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.MathUtils;
import android.view.FocusFinder;
import android.view.Gravity;
import android.view.KeyEvent;
@@ -43,7 +43,6 @@
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
-import android.view.accessibility.AccessibilityRecord;
import android.view.animation.Interpolator;
import android.widget.EdgeEffect;
import android.widget.Scroller;
@@ -84,8 +83,9 @@
*/
public class ViewPager extends ViewGroup {
private static final String TAG = "ViewPager";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = true;
+ private static final int MAX_SCROLL_X = 2 << 23;
private static final boolean USE_CACHE = false;
private static final int DEFAULT_OFFSCREEN_PAGES = 1;
@@ -108,9 +108,13 @@
static class ItemInfo {
Object object;
- int position;
boolean scrolling;
float widthFactor;
+
+ /** Logical position of the item within the pager adapter. */
+ int position;
+
+ /** Offset between the starting edges of the item and its container. */
float offset;
}
@@ -146,6 +150,12 @@
private int mTopPageBounds;
private int mBottomPageBounds;
+ /**
+ * The increment used to move in the "left" direction. Dependent on layout
+ * direction.
+ */
+ private int mLeftIncr = -1;
+
// Offsets of the first and last items, if known.
// Set during population, used to determine if we are at the beginning
// or end of the pager data set during touch scrolling.
@@ -198,14 +208,10 @@
// "catching" the flinging pager.
private static final int CLOSE_ENOUGH = 2; // dp
- private boolean mFakeDragging;
- private long mFakeDragBeginTime;
-
private final EdgeEffect mLeftEdge;
private final EdgeEffect mRightEdge;
private boolean mFirstLayout = true;
- private boolean mNeedCalculatePageOffsets = false;
private boolean mCalledSuper;
private int mDecorChildCount;
@@ -473,7 +479,7 @@
mAdapterChangeListener = listener;
}
- private int getClientWidth() {
+ private int getPaddedWidth() {
return getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
}
@@ -504,36 +510,33 @@
return mCurItem;
}
- void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
- setCurrentItemInternal(item, smoothScroll, always, 0);
+ boolean setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
+ return setCurrentItemInternal(item, smoothScroll, always, 0);
}
- void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
+ boolean setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
if (mAdapter == null || mAdapter.getCount() <= 0) {
setScrollingCacheEnabled(false);
- return;
- }
- if (!always && mCurItem == item && mItems.size() != 0) {
- setScrollingCacheEnabled(false);
- return;
+ return false;
}
- if (item < 0) {
- item = 0;
- } else if (item >= mAdapter.getCount()) {
- item = mAdapter.getCount() - 1;
+ item = MathUtils.constrain(item, 0, mAdapter.getCount() - 1);
+ if (!always && mCurItem == item && mItems.size() != 0) {
+ setScrollingCacheEnabled(false);
+ return false;
}
+
final int pageLimit = mOffscreenPageLimit;
if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {
// We are doing a jump by more than one page. To avoid
// glitches, we want to keep all current pages in the view
// until the scroll ends.
- for (int i=0; i<mItems.size(); i++) {
+ for (int i = 0; i < mItems.size(); i++) {
mItems.get(i).scrolling = true;
}
}
- final boolean dispatchSelected = mCurItem != item;
+ final boolean dispatchSelected = mCurItem != item;
if (mFirstLayout) {
// We don't have any idea how big we are yet and shouldn't have any pages either.
// Just set things up and let the pending layout handle things.
@@ -549,38 +552,55 @@
populate(item);
scrollToItem(item, smoothScroll, velocity, dispatchSelected);
}
+
+ return true;
}
- private void scrollToItem(int item, boolean smoothScroll, int velocity,
+ private void scrollToItem(int position, boolean smoothScroll, int velocity,
boolean dispatchSelected) {
- final ItemInfo curInfo = infoForPosition(item);
- int destX = 0;
- if (curInfo != null) {
- final int width = getClientWidth();
- destX = (int) (width * Math.max(mFirstOffset,
- Math.min(curInfo.offset, mLastOffset)));
- }
+ final int destX = getLeftEdgeForItem(position);
+
if (smoothScroll) {
smoothScrollTo(destX, 0, velocity);
+
if (dispatchSelected && mOnPageChangeListener != null) {
- mOnPageChangeListener.onPageSelected(item);
+ mOnPageChangeListener.onPageSelected(position);
}
if (dispatchSelected && mInternalPageChangeListener != null) {
- mInternalPageChangeListener.onPageSelected(item);
+ mInternalPageChangeListener.onPageSelected(position);
}
} else {
if (dispatchSelected && mOnPageChangeListener != null) {
- mOnPageChangeListener.onPageSelected(item);
+ mOnPageChangeListener.onPageSelected(position);
}
if (dispatchSelected && mInternalPageChangeListener != null) {
- mInternalPageChangeListener.onPageSelected(item);
+ mInternalPageChangeListener.onPageSelected(position);
}
+
completeScroll(false);
scrollTo(destX, 0);
pageScrolled(destX);
}
}
+ private int getLeftEdgeForItem(int position) {
+ final ItemInfo info = infoForPosition(position);
+ if (info == null) {
+ return 0;
+ }
+
+ final int width = getPaddedWidth();
+ final int scaledOffset = (int) (width * MathUtils.constrain(
+ info.offset, mFirstOffset, mLastOffset));
+
+ if (isLayoutRtl()) {
+ final int itemWidth = (int) (width * info.widthFactor + 0.5f);
+ return MAX_SCROLL_X - itemWidth - scaledOffset;
+ } else {
+ return scaledOffset;
+ }
+ }
+
/**
* Set a listener that will be invoked whenever the page changes or is incrementally
* scrolled. See {@link OnPageChangeListener}.
@@ -784,7 +804,7 @@
setScrollingCacheEnabled(true);
setScrollState(SCROLL_STATE_SETTLING);
- final int width = getClientWidth();
+ final int width = getPaddedWidth();
final int halfWidth = width / 2;
final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width);
final float distance = halfWidth + halfWidth *
@@ -968,7 +988,7 @@
float extraWidthLeft = 0.f;
int itemIndex = curIndex - 1;
ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
- final int clientWidth = getClientWidth();
+ final int clientWidth = getPaddedWidth();
final float leftWidthNeeded = clientWidth <= 0 ? 0 :
2.f - curItem.widthFactor + (float) getPaddingLeft() / (float) clientWidth;
for (int pos = mCurItem - 1; pos >= 0; pos--) {
@@ -981,7 +1001,7 @@
mAdapter.destroyItem(this, pos, ii.object);
if (DEBUG) {
Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
- " view: " + ((View) ii.object));
+ " view: " + ii.object);
}
itemIndex--;
curIndex--;
@@ -1015,7 +1035,7 @@
mAdapter.destroyItem(this, pos, ii.object);
if (DEBUG) {
Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
- " view: " + ((View) ii.object));
+ " view: " + ii.object);
}
ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
}
@@ -1099,49 +1119,51 @@
private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo) {
final int N = mAdapter.getCount();
- final int width = getClientWidth();
+ final int width = getPaddedWidth();
final float marginOffset = width > 0 ? (float) mPageMargin / width : 0;
+
// Fix up offsets for later layout.
if (oldCurInfo != null) {
final int oldCurPosition = oldCurInfo.position;
+
// Base offsets off of oldCurInfo.
if (oldCurPosition < curItem.position) {
int itemIndex = 0;
- ItemInfo ii = null;
float offset = oldCurInfo.offset + oldCurInfo.widthFactor + marginOffset;
- for (int pos = oldCurPosition + 1;
- pos <= curItem.position && itemIndex < mItems.size(); pos++) {
- ii = mItems.get(itemIndex);
+ for (int pos = oldCurPosition + 1; pos <= curItem.position && itemIndex < mItems.size(); pos++) {
+ ItemInfo ii = mItems.get(itemIndex);
while (pos > ii.position && itemIndex < mItems.size() - 1) {
itemIndex++;
ii = mItems.get(itemIndex);
}
+
while (pos < ii.position) {
// We don't have an item populated for this,
// ask the adapter for an offset.
offset += mAdapter.getPageWidth(pos) + marginOffset;
pos++;
}
+
ii.offset = offset;
offset += ii.widthFactor + marginOffset;
}
} else if (oldCurPosition > curItem.position) {
int itemIndex = mItems.size() - 1;
- ItemInfo ii = null;
float offset = oldCurInfo.offset;
- for (int pos = oldCurPosition - 1;
- pos >= curItem.position && itemIndex >= 0; pos--) {
- ii = mItems.get(itemIndex);
+ for (int pos = oldCurPosition - 1; pos >= curItem.position && itemIndex >= 0; pos--) {
+ ItemInfo ii = mItems.get(itemIndex);
while (pos < ii.position && itemIndex > 0) {
itemIndex--;
ii = mItems.get(itemIndex);
}
+
while (pos > ii.position) {
// We don't have an item populated for this,
// ask the adapter for an offset.
offset -= mAdapter.getPageWidth(pos) + marginOffset;
pos--;
}
+
offset -= ii.widthFactor + marginOffset;
ii.offset = offset;
}
@@ -1155,6 +1177,7 @@
mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE;
mLastOffset = curItem.position == N - 1 ?
curItem.offset + curItem.widthFactor - 1 : Float.MAX_VALUE;
+
// Previous pages
for (int i = curIndex - 1; i >= 0; i--, pos--) {
final ItemInfo ii = mItems.get(i);
@@ -1165,8 +1188,10 @@
ii.offset = offset;
if (ii.position == 0) mFirstOffset = offset;
}
+
offset = curItem.offset + curItem.widthFactor + marginOffset;
pos = curItem.position + 1;
+
// Next pages
for (int i = curIndex + 1; i < itemCount; i++, pos++) {
final ItemInfo ii = mItems.get(i);
@@ -1179,8 +1204,6 @@
ii.offset = offset;
offset += ii.widthFactor + marginOffset;
}
-
- mNeedCalculatePageOffsets = false;
}
/**
@@ -1546,34 +1569,47 @@
// Page views. Do this once we have the right padding offsets from above.
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
- if (child.getVisibility() != GONE) {
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- ItemInfo ii;
- if (!lp.isDecor && (ii = infoForChild(child)) != null) {
- int loff = (int) (childWidth * ii.offset);
- int childLeft = paddingLeft + loff;
- int childTop = paddingTop;
- if (lp.needsMeasure) {
- // This was added during layout and needs measurement.
- // Do it now that we know what we're working with.
- lp.needsMeasure = false;
- final int widthSpec = MeasureSpec.makeMeasureSpec(
- (int) (childWidth * lp.widthFactor),
- MeasureSpec.EXACTLY);
- final int heightSpec = MeasureSpec.makeMeasureSpec(
- (int) (height - paddingTop - paddingBottom),
- MeasureSpec.EXACTLY);
- child.measure(widthSpec, heightSpec);
- }
- if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object
- + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()
- + "x" + child.getMeasuredHeight());
- child.layout(childLeft, childTop,
- childLeft + child.getMeasuredWidth(),
- childTop + child.getMeasuredHeight());
- }
+ if (child.getVisibility() == GONE) {
+ continue;
}
+
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (lp.isDecor) {
+ continue;
+ }
+
+ final ItemInfo ii = infoForChild(child);
+ if (ii == null) {
+ continue;
+ }
+
+ if (lp.needsMeasure) {
+ // This was added during layout and needs measurement.
+ // Do it now that we know what we're working with.
+ lp.needsMeasure = false;
+ final int widthSpec = MeasureSpec.makeMeasureSpec(
+ (int) (childWidth * lp.widthFactor),
+ MeasureSpec.EXACTLY);
+ final int heightSpec = MeasureSpec.makeMeasureSpec(
+ (int) (height - paddingTop - paddingBottom),
+ MeasureSpec.EXACTLY);
+ child.measure(widthSpec, heightSpec);
+ }
+
+ final int childMeasuredWidth = child.getMeasuredWidth();
+ final int startOffset = (int) (childWidth * ii.offset);
+ final int childLeft;
+ if (isLayoutRtl()) {
+ childLeft = MAX_SCROLL_X - paddingRight - startOffset - childMeasuredWidth;
+ } else {
+ childLeft = paddingLeft + startOffset;
+ }
+
+ final int childTop = paddingTop;
+ child.layout(childLeft, childTop, childLeft + childMeasuredWidth,
+ childTop + child.getMeasuredHeight());
}
+
mTopPageBounds = paddingTop;
mBottomPageBounds = height - paddingBottom;
mDecorChildCount = decorCount;
@@ -1587,13 +1623,14 @@
@Override
public void computeScroll() {
if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
- int oldX = getScrollX();
- int oldY = getScrollY();
- int x = mScroller.getCurrX();
- int y = mScroller.getCurrY();
+ final int oldX = getScrollX();
+ final int oldY = getScrollY();
+ final int x = mScroller.getCurrX();
+ final int y = mScroller.getCurrY();
if (oldX != x || oldY != y) {
scrollTo(x, y);
+
if (!pageScrolled(x)) {
mScroller.abortAnimation();
scrollTo(0, y);
@@ -1609,7 +1646,7 @@
completeScroll(true);
}
- private boolean pageScrolled(int xpos) {
+ private boolean pageScrolled(int scrollX) {
if (mItems.size() == 0) {
mCalledSuper = false;
onPageScrolled(0, 0, 0);
@@ -1619,12 +1656,21 @@
}
return false;
}
- final ItemInfo ii = infoForCurrentScrollPosition();
- final int width = getClientWidth();
+
+ // Translate to scrollX to scrollStart for RTL.
+ final int scrollStart;
+ if (isLayoutRtl()) {
+ scrollStart = MAX_SCROLL_X - scrollX;
+ } else {
+ scrollStart = scrollX;
+ }
+
+ final ItemInfo ii = infoForFirstVisiblePage();
+ final int width = getPaddedWidth();
final int widthWithMargin = width + mPageMargin;
final float marginOffset = (float) mPageMargin / width;
final int currentPage = ii.position;
- final float pageOffset = (((float) xpos / width) - ii.offset) /
+ final float pageOffset = (((float) scrollStart / width) - ii.offset) /
(ii.widthFactor + marginOffset);
final int offsetPixels = (int) (pageOffset * widthWithMargin);
@@ -1706,7 +1752,7 @@
if (lp.isDecor) continue;
- final float transformPos = (float) (child.getLeft() - scrollX) / getClientWidth();
+ final float transformPos = (float) (child.getLeft() - scrollX) / getPaddedWidth();
mPageTransformer.transformPage(child, transformPos);
}
}
@@ -1785,11 +1831,11 @@
// are dragging.
if (action != MotionEvent.ACTION_DOWN) {
if (mIsBeingDragged) {
- if (DEBUG) Log.v(TAG, "Intercept returning true!");
+ if (DEBUG) Log.v(TAG, "Being dragged, intercept returning true!");
return true;
}
if (mIsUnableToDrag) {
- if (DEBUG) Log.v(TAG, "Intercept returning false!");
+ if (DEBUG) Log.v(TAG, "Unable to drag, intercept returning false!");
return false;
}
}
@@ -1903,13 +1949,6 @@
@Override
public boolean onTouchEvent(MotionEvent ev) {
- if (mFakeDragging) {
- // A fake drag is in progress already, ignore this real one
- // but still eat the touch events.
- // (It is likely that the user is multi-touching the screen.)
- return true;
- }
-
if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
// Don't handle edge touches immediately -- they may actually belong to one of our
// descendants.
@@ -1978,19 +2017,26 @@
if (mIsBeingDragged) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
- int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId);
+ final int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId);
+
mPopulatePending = true;
- final int width = getClientWidth();
- final int scrollX = getScrollX();
- final ItemInfo ii = infoForCurrentScrollPosition();
+
+ final float scrollStart = getScrollStart();
+ final float scrolledPages = scrollStart / getPaddedWidth();
+ final ItemInfo ii = infoForFirstVisiblePage();
final int currentPage = ii.position;
- final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;
- final int activePointerIndex =
- ev.findPointerIndex(mActivePointerId);
+ final float nextPageOffset;
+ if (isLayoutRtl()) {
+ nextPageOffset = (ii.offset - scrolledPages) / ii.widthFactor;
+ } else {
+ nextPageOffset = (scrolledPages - ii.offset) / ii.widthFactor;
+ }
+
+ final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
final float x = ev.getX(activePointerIndex);
final int totalDelta = (int) (x - mInitialMotionX);
- int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
- totalDelta);
+ final int nextPage = determineTargetPage(
+ currentPage, nextPageOffset, initialVelocity, totalDelta);
setCurrentItemInternal(nextPage, true, true, initialVelocity);
mActivePointerId = INVALID_POINTER;
@@ -2038,48 +2084,79 @@
private boolean performDrag(float x) {
boolean needsInvalidate = false;
+ final int width = getPaddedWidth();
final float deltaX = mLastMotionX - x;
mLastMotionX = x;
- float oldScrollX = getScrollX();
- float scrollX = oldScrollX + deltaX;
- final int width = getClientWidth();
-
- float leftBound = width * mFirstOffset;
- float rightBound = width * mLastOffset;
- boolean leftAbsolute = true;
- boolean rightAbsolute = true;
-
- final ItemInfo firstItem = mItems.get(0);
- final ItemInfo lastItem = mItems.get(mItems.size() - 1);
- if (firstItem.position != 0) {
- leftAbsolute = false;
- leftBound = firstItem.offset * width;
- }
- if (lastItem.position != mAdapter.getCount() - 1) {
- rightAbsolute = false;
- rightBound = lastItem.offset * width;
+ final EdgeEffect startEdge;
+ final EdgeEffect endEdge;
+ if (isLayoutRtl()) {
+ startEdge = mRightEdge;
+ endEdge = mLeftEdge;
+ } else {
+ startEdge = mLeftEdge;
+ endEdge = mRightEdge;
}
- if (scrollX < leftBound) {
- if (leftAbsolute) {
- float over = leftBound - scrollX;
- mLeftEdge.onPull(Math.abs(over) / width);
+ // Translate scroll to relative coordinates.
+ final float nextScrollX = getScrollX() + deltaX;
+ final float scrollStart;
+ if (isLayoutRtl()) {
+ scrollStart = MAX_SCROLL_X - nextScrollX;
+ } else {
+ scrollStart = nextScrollX;
+ }
+
+ final float startBound;
+ final ItemInfo startItem = mItems.get(0);
+ final boolean startAbsolute = startItem.position == 0;
+ if (startAbsolute) {
+ startBound = startItem.offset * width;
+ } else {
+ startBound = width * mFirstOffset;
+ }
+
+ final float endBound;
+ final ItemInfo endItem = mItems.get(mItems.size() - 1);
+ final boolean endAbsolute = endItem.position == mAdapter.getCount() - 1;
+ if (endAbsolute) {
+ endBound = endItem.offset * width;
+ } else {
+ endBound = width * mLastOffset;
+ }
+
+ final float clampedScrollStart;
+ if (scrollStart < startBound) {
+ if (startAbsolute) {
+ final float over = startBound - scrollStart;
+ startEdge.onPull(Math.abs(over) / width);
needsInvalidate = true;
}
- scrollX = leftBound;
- } else if (scrollX > rightBound) {
- if (rightAbsolute) {
- float over = scrollX - rightBound;
- mRightEdge.onPull(Math.abs(over) / width);
+ clampedScrollStart = startBound;
+ } else if (scrollStart > endBound) {
+ if (endAbsolute) {
+ final float over = scrollStart - endBound;
+ endEdge.onPull(Math.abs(over) / width);
needsInvalidate = true;
}
- scrollX = rightBound;
+ clampedScrollStart = endBound;
+ } else {
+ clampedScrollStart = scrollStart;
}
- // Don't lose the rounded component
- mLastMotionX += scrollX - (int) scrollX;
- scrollTo((int) scrollX, getScrollY());
- pageScrolled((int) scrollX);
+
+ // Translate back to absolute coordinates.
+ final float targetScrollX;
+ if (isLayoutRtl()) {
+ targetScrollX = MAX_SCROLL_X - clampedScrollStart;
+ } else {
+ targetScrollX = clampedScrollStart;
+ }
+
+ // Don't lose the rounded component.
+ mLastMotionX += targetScrollX - (int) targetScrollX;
+
+ scrollTo((int) targetScrollX, getScrollY());
+ pageScrolled((int) targetScrollX);
return needsInvalidate;
}
@@ -2088,19 +2165,23 @@
* @return Info about the page at the current scroll position.
* This can be synthetic for a missing middle page; the 'object' field can be null.
*/
- private ItemInfo infoForCurrentScrollPosition() {
- final int width = getClientWidth();
- final float scrollOffset = width > 0 ? (float) getScrollX() / width : 0;
+ private ItemInfo infoForFirstVisiblePage() {
+ final int startOffset = getScrollStart();
+ final int width = getPaddedWidth();
+ final float scrollOffset = width > 0 ? (float) startOffset / width : 0;
final float marginOffset = width > 0 ? (float) mPageMargin / width : 0;
+
int lastPos = -1;
float lastOffset = 0.f;
float lastWidth = 0.f;
boolean first = true;
-
ItemInfo lastItem = null;
- for (int i = 0; i < mItems.size(); i++) {
+
+ final int N = mItems.size();
+ for (int i = 0; i < N; i++) {
ItemInfo ii = mItems.get(i);
- float offset;
+
+ // Seek to position.
if (!first && ii.position != lastPos + 1) {
// Create a synthetic item for a missing page.
ii = mTempItem;
@@ -2109,17 +2190,18 @@
ii.widthFactor = mAdapter.getPageWidth(ii.position);
i--;
}
- offset = ii.offset;
- final float leftBound = offset;
- final float rightBound = offset + ii.widthFactor + marginOffset;
- if (first || scrollOffset >= leftBound) {
- if (scrollOffset < rightBound || i == mItems.size() - 1) {
+ final float offset = ii.offset;
+ final float startBound = offset;
+ if (first || scrollOffset >= startBound) {
+ final float endBound = offset + ii.widthFactor + marginOffset;
+ if (scrollOffset < endBound || i == mItems.size() - 1) {
return ii;
}
} else {
return lastItem;
}
+
first = false;
lastPos = ii.position;
lastOffset = offset;
@@ -2130,13 +2212,28 @@
return lastItem;
}
+ private int getScrollStart() {
+ if (isLayoutRtl()) {
+ return MAX_SCROLL_X - getScrollX();
+ } else {
+ return getScrollX();
+ }
+ }
+
+ /**
+ * @param currentPage the position of the page with the first visible starting edge
+ * @param pageOffset the fraction of the right-hand page that's visible
+ * @param velocity the velocity of the touch event stream
+ * @param deltaX the distance of the touch event stream
+ * @return the position of the target page
+ */
private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaX) {
int targetPage;
if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {
- targetPage = velocity > 0 ? currentPage : currentPage + 1;
+ targetPage = currentPage - (velocity < 0 ? mLeftIncr : 0);
} else {
final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f;
- targetPage = (int) (currentPage + pageOffset + truncator);
+ targetPage = (int) (currentPage - mLeftIncr * (pageOffset + truncator));
}
if (mItems.size() > 0) {
@@ -2144,7 +2241,7 @@
final ItemInfo lastItem = mItems.get(mItems.size() - 1);
// Only let the user target pages we have items for
- targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position));
+ targetPage = MathUtils.constrain(targetPage, firstItem.position, lastItem.position);
}
return targetPage;
@@ -2205,6 +2302,7 @@
int itemIndex = 0;
ItemInfo ii = mItems.get(0);
float offset = ii.offset;
+
final int itemCount = mItems.size();
final int firstPos = ii.position;
final int lastPos = mItems.get(itemCount - 1).position;
@@ -2213,156 +2311,39 @@
ii = mItems.get(++itemIndex);
}
- float drawAt;
+ final float itemOffset;
+ final float widthFactor;
if (pos == ii.position) {
- drawAt = (ii.offset + ii.widthFactor) * width;
- offset = ii.offset + ii.widthFactor + marginOffset;
+ itemOffset = ii.offset;
+ widthFactor = ii.widthFactor;
} else {
- float widthFactor = mAdapter.getPageWidth(pos);
- drawAt = (offset + widthFactor) * width;
- offset += widthFactor + marginOffset;
+ itemOffset = offset;
+ widthFactor = mAdapter.getPageWidth(pos);
}
- if (drawAt + mPageMargin > scrollX) {
- mMarginDrawable.setBounds((int) drawAt, mTopPageBounds,
- (int) (drawAt + mPageMargin + 0.5f), mBottomPageBounds);
+ final float left;
+ final float scaledOffset = itemOffset * width;
+ if (isLayoutRtl()) {
+ left = MAX_SCROLL_X - scaledOffset;
+ } else {
+ left = scaledOffset + widthFactor * width;
+ }
+
+ offset = itemOffset + widthFactor + marginOffset;
+
+ if (left + mPageMargin > scrollX) {
+ mMarginDrawable.setBounds((int) left, mTopPageBounds,
+ (int) (left + mPageMargin + 0.5f), mBottomPageBounds);
mMarginDrawable.draw(canvas);
}
- if (drawAt > scrollX + width) {
+ if (left > scrollX + width) {
break; // No more visible, no sense in continuing
}
}
}
}
- /**
- * Start a fake drag of the pager.
- *
- * <p>A fake drag can be useful if you want to synchronize the motion of the ViewPager
- * with the touch scrolling of another view, while still letting the ViewPager
- * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.)
- * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call
- * {@link #endFakeDrag()} to complete the fake drag and fling as necessary.
- *
- * <p>During a fake drag the ViewPager will ignore all touch events. If a real drag
- * is already in progress, this method will return false.
- *
- * @return true if the fake drag began successfully, false if it could not be started.
- *
- * @see #fakeDragBy(float)
- * @see #endFakeDrag()
- */
- public boolean beginFakeDrag() {
- if (mIsBeingDragged) {
- return false;
- }
- mFakeDragging = true;
- setScrollState(SCROLL_STATE_DRAGGING);
- mInitialMotionX = mLastMotionX = 0;
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- } else {
- mVelocityTracker.clear();
- }
- final long time = SystemClock.uptimeMillis();
- final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0);
- mVelocityTracker.addMovement(ev);
- ev.recycle();
- mFakeDragBeginTime = time;
- return true;
- }
-
- /**
- * End a fake drag of the pager.
- *
- * @see #beginFakeDrag()
- * @see #fakeDragBy(float)
- */
- public void endFakeDrag() {
- if (!mFakeDragging) {
- throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
- }
-
- final VelocityTracker velocityTracker = mVelocityTracker;
- velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
- int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId);
- mPopulatePending = true;
- final int width = getClientWidth();
- final int scrollX = getScrollX();
- final ItemInfo ii = infoForCurrentScrollPosition();
- final int currentPage = ii.position;
- final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;
- final int totalDelta = (int) (mLastMotionX - mInitialMotionX);
- int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
- totalDelta);
- setCurrentItemInternal(nextPage, true, true, initialVelocity);
- endDrag();
-
- mFakeDragging = false;
- }
-
- /**
- * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first.
- *
- * @param xOffset Offset in pixels to drag by.
- * @see #beginFakeDrag()
- * @see #endFakeDrag()
- */
- public void fakeDragBy(float xOffset) {
- if (!mFakeDragging) {
- throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
- }
-
- mLastMotionX += xOffset;
-
- float oldScrollX = getScrollX();
- float scrollX = oldScrollX - xOffset;
- final int width = getClientWidth();
-
- float leftBound = width * mFirstOffset;
- float rightBound = width * mLastOffset;
-
- final ItemInfo firstItem = mItems.get(0);
- final ItemInfo lastItem = mItems.get(mItems.size() - 1);
- if (firstItem.position != 0) {
- leftBound = firstItem.offset * width;
- }
- if (lastItem.position != mAdapter.getCount() - 1) {
- rightBound = lastItem.offset * width;
- }
-
- if (scrollX < leftBound) {
- scrollX = leftBound;
- } else if (scrollX > rightBound) {
- scrollX = rightBound;
- }
- // Don't lose the rounded component
- mLastMotionX += scrollX - (int) scrollX;
- scrollTo((int) scrollX, getScrollY());
- pageScrolled((int) scrollX);
-
- // Synthesize an event for the VelocityTracker.
- final long time = SystemClock.uptimeMillis();
- final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,
- mLastMotionX, 0, 0);
- mVelocityTracker.addMovement(ev);
- ev.recycle();
- }
-
- /**
- * Returns true if a fake drag is in progress.
- *
- * @return true if currently in a fake drag, false otherwise.
- *
- * @see #beginFakeDrag()
- * @see #fakeDragBy(float)
- * @see #endFakeDrag()
- */
- public boolean isFakeDragging() {
- return mFakeDragging;
- }
-
private void onSecondaryPointerUp(MotionEvent ev) {
final int pointerIndex = ev.getActionIndex();
final int pointerId = ev.getPointerId(pointerIndex);
@@ -2408,7 +2389,7 @@
return false;
}
- final int width = getClientWidth();
+ final int width = getPaddedWidth();
final int scrollX = getScrollX();
if (direction < 0) {
return (scrollX > (int) (width * mFirstOffset));
@@ -2438,12 +2419,11 @@
final int count = group.getChildCount();
// Count backwards - let topmost views consume scroll distance first.
for (int i = count - 1; i >= 0; i--) {
- // TODO: Add versioned support here for transformed views.
- // This will not work for transformed views in Honeycomb+
+ // TODO: Add support for transformed views.
final View child = group.getChildAt(i);
- if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
- y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
- canScroll(child, true, dx, x + scrollX - child.getLeft(),
+ if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight()
+ && y + scrollY >= child.getTop() && y + scrollY < child.getBottom()
+ && canScroll(child, true, dx, x + scrollX - child.getLeft(),
y + scrollY - child.getTop())) {
return true;
}
@@ -2582,19 +2562,22 @@
}
boolean pageLeft() {
- if (mCurItem > 0) {
- setCurrentItem(mCurItem-1, true);
- return true;
- }
- return false;
+ return setCurrentItemInternal(mCurItem + mLeftIncr, true, false);
}
boolean pageRight() {
- if (mAdapter != null && mCurItem < (mAdapter.getCount()-1)) {
- setCurrentItem(mCurItem+1, true);
- return true;
+ return setCurrentItemInternal(mCurItem - mLeftIncr, true, false);
+ }
+
+ @Override
+ public void onRtlPropertiesChanged(@ResolvedLayoutDir int layoutDirection) {
+ super.onRtlPropertiesChanged(layoutDirection);
+
+ if (layoutDirection == LAYOUT_DIRECTION_LTR) {
+ mLeftIncr = -1;
+ } else {
+ mLeftIncr = 1;
}
- return false;
}
/**
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 62685a1..dced051 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1309,6 +1309,14 @@
<permission android:name="android.permission.MANAGE_USERS"
android:protectionLevel="signature|system" />
+ <!-- @hide Allows an application to set the profile owners and the device owner.
+ This permission is not available to third party applications.-->
+ <permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS"
+ android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+ android:protectionLevel="signature"
+ android:label="@string/permlab_manageProfileAndDeviceOwners"
+ android:description="@string/permdesc_manageProfileAndDeviceOwners" />
+
<!-- Allows an application to get full detailed information about
recently running tasks, with full fidelity to the real state.
@hide -->
diff --git a/core/res/res/drawable/ic_chevron_right.xml b/core/res/res/drawable/ic_chevron_end.xml
similarity index 91%
rename from core/res/res/drawable/ic_chevron_right.xml
rename to core/res/res/drawable/ic_chevron_end.xml
index 4e6d8e3..8570d26 100644
--- a/core/res/res/drawable/ic_chevron_right.xml
+++ b/core/res/res/drawable/ic_chevron_end.xml
@@ -18,7 +18,8 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
- android:tint="?attr/colorControlNormal">
+ android:tint="?attr/colorControlNormal"
+ android:autoMirrored="true">
<path
android:fillColor="#FF000000"
android:pathData="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6,-6z"/>
diff --git a/core/res/res/drawable/ic_chevron_left.xml b/core/res/res/drawable/ic_chevron_start.xml
similarity index 91%
rename from core/res/res/drawable/ic_chevron_left.xml
rename to core/res/res/drawable/ic_chevron_start.xml
index dc24706..d412ce0 100644
--- a/core/res/res/drawable/ic_chevron_left.xml
+++ b/core/res/res/drawable/ic_chevron_start.xml
@@ -18,7 +18,8 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
- android:tint="?attr/colorControlNormal">
+ android:tint="?attr/colorControlNormal"
+ android:autoMirrored="true">
<path
android:fillColor="#FF000000"
android:pathData="M15.41 7.41L14 6l-6 6 6 6 1.41,-1.41L10.83 12z"/>
diff --git a/core/res/res/layout/date_picker_header_material.xml b/core/res/res/layout/date_picker_header_material.xml
index 8125544..2150341 100644
--- a/core/res/res/layout/date_picker_header_material.xml
+++ b/core/res/res/layout/date_picker_header_material.xml
@@ -42,7 +42,7 @@
<TextView
android:id="@+id/date_picker_header_date"
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Material.DatePicker.DateLabel"
android:gravity="start"
diff --git a/core/res/res/layout/day_picker_content_material.xml b/core/res/res/layout/day_picker_content_material.xml
index 1852bfa..b582d74 100644
--- a/core/res/res/layout/day_picker_content_material.xml
+++ b/core/res/res/layout/day_picker_content_material.xml
@@ -30,7 +30,7 @@
android:layout_height="wrap_content"
android:minWidth="48dp"
android:minHeight="48dp"
- android:src="@drawable/ic_chevron_left"
+ android:src="@drawable/ic_chevron_start"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/date_picker_prev_month_button"
android:visibility="invisible" />
@@ -41,7 +41,7 @@
android:layout_height="wrap_content"
android:minWidth="48dp"
android:minHeight="48dp"
- android:src="@drawable/ic_chevron_right"
+ android:src="@drawable/ic_chevron_end"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/date_picker_next_month_button"
android:visibility="invisible" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 6f554f08..51c2062 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -699,6 +699,12 @@
discover information about which applications are used on the device.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_manageProfileAndDeviceOwners">Manage profile and device owners</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to set the profile/device owners.
+ [CHAR LIMIT=NONE] -->
+ <string name="permdesc_manageProfileAndDeviceOwners">Allows apps to set the profile owners and the device owner.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_reorderTasks">reorder running apps</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_reorderTasks">Allows the app to move tasks to the
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7e24150..5b13325 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2235,8 +2235,6 @@
<java-symbol type="dimen" name="floating_toolbar_horizontal_margin" />
<java-symbol type="dimen" name="floating_toolbar_vertical_margin" />
- <java-symbol type="drawable" name="ic_chevron_left" />
- <java-symbol type="drawable" name="ic_chevron_right" />
<java-symbol type="string" name="date_picker_prev_month_button" />
<java-symbol type="string" name="date_picker_next_month_button" />
<java-symbol type="layout" name="date_picker_month_item_material" />
diff --git a/docs/html/guide/appendix/media-formats.jd b/docs/html/guide/appendix/media-formats.jd
index 19f510a..2a908ba 100644
--- a/docs/html/guide/appendix/media-formats.jd
+++ b/docs/html/guide/appendix/media-formats.jd
@@ -71,7 +71,7 @@
</tr>
<tr>
-<td rowspan="11">Audio</td>
+<td rowspan="12">Audio</td>
<td>AAC LC</td>
<td style="text-align: center;"><big>•</big></td>
<td style="text-align: center;"><big>•</big></td>
@@ -180,6 +180,15 @@
</tr>
<tr>
+<td>Opus</td>
+<td style="text-align: center;"></td>
+<td style="text-align: center;"><big>•</big><br><small>(Android 5.0+)</small></td>
+<td></td>
+<td>
+ Matroska (.mkv)</td>
+</tr>
+
+<tr>
<td rowspan="5">Image</td>
<td>JPEG</td>
<td style="text-align: center;"><big>•</big></td>
@@ -235,7 +244,7 @@
<tr>
-<td rowspan="4">Video</td>
+<td rowspan="6">Video</td>
<td>H.263</td>
<td style="text-align: center;"><big>•</big></td>
<td style="text-align: center;"><big>•</big></td>
@@ -257,6 +266,15 @@
</tr>
<tr>
+<td>H.265 HEVC</td>
+<td style="text-align: center;" nowrap></td>
+<td style="text-align: center;" nowrap><big>•</big><br><small>(Android 5.0+)</small></td>
+<td>Main Profile Level 3 for mobile devices and Main Profile Level 4.1 for Android TV</td>
+<td>
+ • MPEG-4 (.mp4)<br>
+</tr>
+
+<tr>
<td>MPEG-4 SP</td>
<td> </td>
<td style="text-align: center;"><big>•</big></td>
@@ -275,6 +293,16 @@
• Matroska (.mkv, Android 4.0+)</td>
</tr>
+<tr>
+<td>VP9</td>
+<td style="text-align: center;" nowrap></td>
+<td style="text-align: center;" nowrap><big>•</big><br><small>(Android 4.4+)</small></td>
+<td></td>
+<td>
+ • <a href="http://www.webmproject.org/">WebM</a> (.webm)<br>
+ • Matroska (.mkv, Android 4.0+)</td>
+</tr>
+
</tbody></table>
diff --git a/docs/html/training/tv/start/start.jd b/docs/html/training/tv/start/start.jd
index 2766e90..0f5871f 100644
--- a/docs/html/training/tv/start/start.jd
+++ b/docs/html/training/tv/start/start.jd
@@ -1,4 +1,4 @@
-page.title=Get Started with TV Apps
+page.title=Getting Started with TV Apps
page.tags="leanback","recyclerview","launcher"
trainingnavtop=true
@@ -10,6 +10,7 @@
<div id="tb">
<h2>This lesson teaches you how to</h2>
<ol>
+ <li><a href="#media">Determine Media Format Support</a></li>
<li><a href="#dev-project">Setup a TV Project</a></li>
<li><a href="#tv-libraries">Add TV Support Libraries</a></li>
<li><a href="#build-it">Build TV Apps</a></li>
@@ -42,6 +43,18 @@
minimum required changes to enable an app to run on TV devices.
</p>
+<h2 id="media">Determine Media Format Support</h2>
+
+<p>See the following documentation for information about the codecs, protocols, and formats
+supported by Android TV.</p>
+
+<ul>
+ <li><a href="{@docRoot}guide/appendix/media-formats.html">Supported Media Formats</a></li>
+ <li><a class="external-link" href="https://source.android.com/devices/drm.html">DRM</a></li>
+ <li><code><a href="{@docRoot}reference/android/drm/package-summary.html">android.drm</a></code></li>
+ <li><a href="{@docRoot}guide/topics/media/exoplayer.html">ExoPlayer</a></li>
+ <li>{@link android.media.MediaPlayer android.media.MediaPlayer}</li>
+</ul>
<h2 id="dev-project">Set up a TV Project</h2>
@@ -284,9 +297,15 @@
TV devices.
</li>
<li>
- <a href="{@docRoot}training/tv/games/index.html">Games for TV</a> - TV devices are a great
+ <a href="{@docRoot}training/tv/games/index.html">Building TV Games</a> - TV devices are a great
platform for games. See this topic for information on building great game experiences for TV.
</li>
+ <li>
+ <a href="{@docRoot}training/tv/tif/index.html">Building Live TV Apps</a> - Present your video
+ content in a linear, "broadcast TV" style with channels and programs that your users can access
+ through a program guide as well as the channel up/down buttons.
+ </li>
+
</ul>
diff --git a/keystore/java/android/security/KeyStoreCipherSpi.java b/keystore/java/android/security/KeyStoreCipherSpi.java
index 37e00b2..1f8d8ec 100644
--- a/keystore/java/android/security/KeyStoreCipherSpi.java
+++ b/keystore/java/android/security/KeyStoreCipherSpi.java
@@ -152,29 +152,58 @@
@Override
protected void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
- init(opmode, key, random);
- initAlgorithmSpecificParameters();
- ensureKeystoreOperationInitialized();
+ resetAll();
+
+ boolean success = false;
+ try {
+ init(opmode, key, random);
+ initAlgorithmSpecificParameters();
+ ensureKeystoreOperationInitialized();
+ success = true;
+ } finally {
+ if (!success) {
+ resetAll();
+ }
+ }
}
@Override
protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random)
throws InvalidKeyException, InvalidAlgorithmParameterException {
- init(opmode, key, random);
- initAlgorithmSpecificParameters(params);
- ensureKeystoreOperationInitialized();
+ resetAll();
+
+ boolean success = false;
+ try {
+ init(opmode, key, random);
+ initAlgorithmSpecificParameters(params);
+ ensureKeystoreOperationInitialized();
+ success = true;
+ } finally {
+ if (!success) {
+ resetAll();
+ }
+ }
}
@Override
protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params,
SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
- init(opmode, key, random);
- initAlgorithmSpecificParameters(params);
- ensureKeystoreOperationInitialized();
+ resetAll();
+
+ boolean success = false;
+ try {
+ init(opmode, key, random);
+ initAlgorithmSpecificParameters(params);
+ ensureKeystoreOperationInitialized();
+ success = true;
+ } finally {
+ if (!success) {
+ resetAll();
+ }
+ }
}
private void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
- resetAll();
if (!(key instanceof KeyStoreSecretKey)) {
throw new InvalidKeyException(
"Unsupported key: " + ((key != null) ? key.getClass().getName() : "null"));
diff --git a/keystore/java/android/security/KeyStoreHmacSpi.java b/keystore/java/android/security/KeyStoreHmacSpi.java
index a19bbda..f8b6fef 100644
--- a/keystore/java/android/security/KeyStoreHmacSpi.java
+++ b/keystore/java/android/security/KeyStoreHmacSpi.java
@@ -69,9 +69,10 @@
private final int mKeymasterDigest;
private final int mMacSizeBytes;
- private String mKeyAliasInKeyStore;
+ // Fields below are populated by engineInit and should be preserved after engineDoFinal.
+ private KeyStoreSecretKey mKey;
- // The fields below are reset by the engineReset operation.
+ // Fields below are reset when engineDoFinal succeeds.
private KeyStoreCryptoOperationChunkedStreamer mChunkedStreamer;
private IBinder mOperationToken;
private Long mOperationHandle;
@@ -89,28 +90,39 @@
@Override
protected void engineInit(Key key, AlgorithmParameterSpec params) throws InvalidKeyException,
InvalidAlgorithmParameterException {
+ resetAll();
+
+ boolean success = false;
+ try {
+ init(key, params);
+ ensureKeystoreOperationInitialized();
+ success = true;
+ } finally {
+ if (!success) {
+ resetAll();
+ }
+ }
+ }
+
+ private void init(Key key, AlgorithmParameterSpec params) throws InvalidKeyException,
+ InvalidAlgorithmParameterException {
if (key == null) {
throw new InvalidKeyException("key == null");
} else if (!(key instanceof KeyStoreSecretKey)) {
throw new InvalidKeyException(
"Only Android KeyStore secret keys supported. Key: " + key);
}
+ mKey = (KeyStoreSecretKey) key;
if (params != null) {
throw new InvalidAlgorithmParameterException(
"Unsupported algorithm parameters: " + params);
}
- mKeyAliasInKeyStore = ((KeyStoreSecretKey) key).getAlias();
- if (mKeyAliasInKeyStore == null) {
- throw new InvalidKeyException("Key's KeyStore alias not known");
- }
- engineReset();
- ensureKeystoreOperationInitialized();
}
- @Override
- protected void engineReset() {
+ private void resetAll() {
+ mKey = null;
IBinder operationToken = mOperationToken;
if (operationToken != null) {
mOperationToken = null;
@@ -120,11 +132,26 @@
mChunkedStreamer = null;
}
+ private void resetWhilePreservingInitState() {
+ IBinder operationToken = mOperationToken;
+ if (operationToken != null) {
+ mOperationToken = null;
+ mKeyStore.abort(operationToken);
+ }
+ mOperationHandle = null;
+ mChunkedStreamer = null;
+ }
+
+ @Override
+ protected void engineReset() {
+ resetWhilePreservingInitState();
+ }
+
private void ensureKeystoreOperationInitialized() {
if (mChunkedStreamer != null) {
return;
}
- if (mKeyAliasInKeyStore == null) {
+ if (mKey == null) {
throw new IllegalStateException("Not initialized");
}
@@ -132,7 +159,8 @@
keymasterArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_HMAC);
keymasterArgs.addInt(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest);
- OperationResult opResult = mKeyStore.begin(mKeyAliasInKeyStore,
+ OperationResult opResult = mKeyStore.begin(
+ mKey.getAlias(),
KeymasterDefs.KM_PURPOSE_SIGN,
true,
keymasterArgs,
@@ -184,7 +212,7 @@
throw KeyStore.getCryptoOperationException(e);
}
- engineReset();
+ resetWhilePreservingInitState();
return result;
}
diff --git a/libs/hwui/Android.common.mk b/libs/hwui/Android.common.mk
index 5fca8ec..836f868 100644
--- a/libs/hwui/Android.common.mk
+++ b/libs/hwui/Android.common.mk
@@ -24,6 +24,7 @@
thread/TaskManager.cpp \
utils/Blur.cpp \
utils/GLUtils.cpp \
+ utils/LinearAllocator.cpp \
utils/SortedListImpl.cpp \
AmbientShadow.cpp \
AnimationContext.cpp \
diff --git a/libs/hwui/DeferredDisplayList.h b/libs/hwui/DeferredDisplayList.h
index c92ab91..f535afb 100644
--- a/libs/hwui/DeferredDisplayList.h
+++ b/libs/hwui/DeferredDisplayList.h
@@ -127,7 +127,7 @@
}
void tryRecycleState(DeferredDisplayState* state) {
- mAllocator.rewindIfLastAlloc(state, sizeof(DeferredDisplayState));
+ mAllocator.rewindIfLastAlloc(state);
}
/**
diff --git a/libs/hwui/tests/Android.mk b/libs/hwui/tests/Android.mk
index 51898d2..b6f0baf 100644
--- a/libs/hwui/tests/Android.mk
+++ b/libs/hwui/tests/Android.mk
@@ -34,16 +34,3 @@
tests/main.cpp
include $(BUILD_EXECUTABLE)
-
-include $(CLEAR_VARS)
-
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.common.mk
-LOCAL_MODULE := hwui_unit_tests
-LOCAL_MODULE_TAGS := tests
-
-include $(LOCAL_PATH)/Android.common.mk
-
-LOCAL_SRC_FILES += \
- tests/ClipAreaTests.cpp \
-
-include $(BUILD_NATIVE_TEST)
diff --git a/libs/hwui/unit_tests/Android.mk b/libs/hwui/unit_tests/Android.mk
new file mode 100644
index 0000000..51601b0
--- /dev/null
+++ b/libs/hwui/unit_tests/Android.mk
@@ -0,0 +1,34 @@
+#
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+local_target_dir := $(TARGET_OUT_DATA)/local/tmp
+LOCAL_PATH:= $(call my-dir)/..
+
+include $(CLEAR_VARS)
+
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.common.mk
+LOCAL_MODULE := hwui_unit_tests
+LOCAL_MODULE_TAGS := tests
+
+include $(LOCAL_PATH)/Android.common.mk
+
+LOCAL_SRC_FILES += \
+ unit_tests/ClipAreaTests.cpp \
+ unit_tests/LinearAllocatorTests.cpp \
+ unit_tests/main.cpp
+
+
+include $(BUILD_NATIVE_TEST)
diff --git a/libs/hwui/tests/ClipAreaTests.cpp b/libs/hwui/unit_tests/ClipAreaTests.cpp
similarity index 100%
rename from libs/hwui/tests/ClipAreaTests.cpp
rename to libs/hwui/unit_tests/ClipAreaTests.cpp
diff --git a/libs/hwui/unit_tests/LinearAllocatorTests.cpp b/libs/hwui/unit_tests/LinearAllocatorTests.cpp
new file mode 100644
index 0000000..b3959d1
--- /dev/null
+++ b/libs/hwui/unit_tests/LinearAllocatorTests.cpp
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+#include <utils/LinearAllocator.h>
+
+using namespace android;
+using namespace android::uirenderer;
+
+struct SimplePair {
+ int one = 1;
+ int two = 2;
+};
+
+class SignalingDtor {
+public:
+ SignalingDtor() {
+ mDestroyed = nullptr;
+ }
+ SignalingDtor(bool* destroyedSignal) {
+ mDestroyed = destroyedSignal;
+ *mDestroyed = false;
+ }
+ virtual ~SignalingDtor() {
+ if (mDestroyed) {
+ *mDestroyed = true;
+ }
+ }
+ void setSignal(bool* destroyedSignal) {
+ mDestroyed = destroyedSignal;
+ }
+private:
+ bool* mDestroyed;
+};
+
+TEST(LinearAllocator, alloc) {
+ LinearAllocator la;
+ EXPECT_EQ(0u, la.usedSize());
+ la.alloc(64);
+ // There's some internal tracking as well as padding
+ // so the usedSize isn't strictly defined
+ EXPECT_LE(64u, la.usedSize());
+ EXPECT_GT(80u, la.usedSize());
+ auto pair = la.alloc<SimplePair>();
+ EXPECT_LE(64u + sizeof(SimplePair), la.usedSize());
+ EXPECT_GT(80u + sizeof(SimplePair), la.usedSize());
+ EXPECT_EQ(1, pair->one);
+ EXPECT_EQ(2, pair->two);
+}
+
+TEST(LinearAllocator, dtor) {
+ bool destroyed[10];
+ {
+ LinearAllocator la;
+ for (int i = 0; i < 5; i++) {
+ la.alloc<SignalingDtor>()->setSignal(destroyed + i);
+ la.alloc<SimplePair>();
+ }
+ la.alloc(100);
+ for (int i = 0; i < 5; i++) {
+ auto sd = new (la) SignalingDtor(destroyed + 5 + i);
+ la.autoDestroy(sd);
+ new (la) SimplePair();
+ }
+ la.alloc(100);
+ for (int i = 0; i < 10; i++) {
+ EXPECT_FALSE(destroyed[i]);
+ }
+ }
+ for (int i = 0; i < 10; i++) {
+ EXPECT_TRUE(destroyed[i]);
+ }
+}
+
+TEST(LinearAllocator, rewind) {
+ bool destroyed;
+ {
+ LinearAllocator la;
+ auto addr = la.alloc(100);
+ EXPECT_LE(100u, la.usedSize());
+ la.rewindIfLastAlloc(addr, 100);
+ EXPECT_GT(16u, la.usedSize());
+ size_t emptySize = la.usedSize();
+ auto sigdtor = la.alloc<SignalingDtor>();
+ sigdtor->setSignal(&destroyed);
+ EXPECT_FALSE(destroyed);
+ EXPECT_LE(emptySize, la.usedSize());
+ la.rewindIfLastAlloc(sigdtor);
+ EXPECT_TRUE(destroyed);
+ EXPECT_EQ(emptySize, la.usedSize());
+ destroyed = false;
+ }
+ // Checking for a double-destroy case
+ EXPECT_EQ(destroyed, false);
+}
diff --git a/libs/hwui/unit_tests/how_to_run.txt b/libs/hwui/unit_tests/how_to_run.txt
new file mode 100755
index 0000000..a2d6a34
--- /dev/null
+++ b/libs/hwui/unit_tests/how_to_run.txt
@@ -0,0 +1,4 @@
+mmm -j8 $ANDROID_BUILD_TOP/frameworks/base/libs/hwui/unit_tests &&
+adb push $ANDROID_PRODUCT_OUT/data/nativetest/hwui_unit_tests/hwui_unit_tests \
+ /data/nativetest/hwui_unit_tests/hwui_unit_tests &&
+adb shell /data/nativetest/hwui_unit_tests/hwui_unit_tests
diff --git a/libs/hwui/unit_tests/main.cpp b/libs/hwui/unit_tests/main.cpp
new file mode 100644
index 0000000..c9b9636
--- /dev/null
+++ b/libs/hwui/unit_tests/main.cpp
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+int main(int argc, char **argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/libs/hwui/utils/LinearAllocator.cpp b/libs/hwui/utils/LinearAllocator.cpp
new file mode 100644
index 0000000..59b12cf
--- /dev/null
+++ b/libs/hwui/utils/LinearAllocator.cpp
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2012, The Android Open Source Project
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define LOG_NDEBUG 1
+
+#include "utils/LinearAllocator.h"
+
+#include <stdlib.h>
+#include <utils/Log.h>
+
+
+// The ideal size of a page allocation (these need to be multiples of 8)
+#define INITIAL_PAGE_SIZE ((size_t)4096) // 4kb
+#define MAX_PAGE_SIZE ((size_t)131072) // 128kb
+
+// The maximum amount of wasted space we can have per page
+// Allocations exceeding this will have their own dedicated page
+// If this is too low, we will malloc too much
+// Too high, and we may waste too much space
+// Must be smaller than INITIAL_PAGE_SIZE
+#define MAX_WASTE_SIZE ((size_t)1024)
+
+#if ALIGN_DOUBLE
+#define ALIGN_SZ (sizeof(double))
+#else
+#define ALIGN_SZ (sizeof(int))
+#endif
+
+#define ALIGN(x) ((x + ALIGN_SZ - 1 ) & ~(ALIGN_SZ - 1))
+#define ALIGN_PTR(p) ((void*)(ALIGN((size_t)p)))
+
+#if LOG_NDEBUG
+#define ADD_ALLOCATION(size)
+#define RM_ALLOCATION(size)
+#else
+#include <utils/Thread.h>
+#include <utils/Timers.h>
+static size_t s_totalAllocations = 0;
+static nsecs_t s_nextLog = 0;
+static android::Mutex s_mutex;
+
+static void _logUsageLocked() {
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+ if (now > s_nextLog) {
+ s_nextLog = now + milliseconds_to_nanoseconds(10);
+ ALOGV("Total memory usage: %zu kb", s_totalAllocations / 1024);
+ }
+}
+
+static void _addAllocation(size_t size) {
+ android::AutoMutex lock(s_mutex);
+ s_totalAllocations += size;
+ _logUsageLocked();
+}
+
+#define ADD_ALLOCATION(size) _addAllocation(size);
+#define RM_ALLOCATION(size) _addAllocation(-size);
+#endif
+
+#define min(x,y) (((x) < (y)) ? (x) : (y))
+
+void* operator new(std::size_t size, android::uirenderer::LinearAllocator& la) {
+ return la.alloc(size);
+}
+
+namespace android {
+namespace uirenderer {
+
+class LinearAllocator::Page {
+public:
+ Page* next() { return mNextPage; }
+ void setNext(Page* next) { mNextPage = next; }
+
+ Page()
+ : mNextPage(0)
+ {}
+
+ void* operator new(size_t /*size*/, void* buf) { return buf; }
+
+ void* start() {
+ return (void*) (((size_t)this) + sizeof(Page));
+ }
+
+ void* end(int pageSize) {
+ return (void*) (((size_t)start()) + pageSize);
+ }
+
+private:
+ Page(const Page& /*other*/) {}
+ Page* mNextPage;
+};
+
+LinearAllocator::LinearAllocator()
+ : mPageSize(INITIAL_PAGE_SIZE)
+ , mMaxAllocSize(MAX_WASTE_SIZE)
+ , mNext(0)
+ , mCurrentPage(0)
+ , mPages(0)
+ , mTotalAllocated(0)
+ , mWastedSpace(0)
+ , mPageCount(0)
+ , mDedicatedPageCount(0) {}
+
+LinearAllocator::~LinearAllocator(void) {
+ while (mDtorList) {
+ auto node = mDtorList;
+ mDtorList = node->next;
+ node->dtor(node->addr);
+ }
+ Page* p = mPages;
+ while (p) {
+ Page* next = p->next();
+ p->~Page();
+ free(p);
+ RM_ALLOCATION(mPageSize);
+ p = next;
+ }
+}
+
+void* LinearAllocator::start(Page* p) {
+ return ALIGN_PTR(((size_t*)p) + sizeof(Page));
+}
+
+void* LinearAllocator::end(Page* p) {
+ return ((char*)p) + mPageSize;
+}
+
+bool LinearAllocator::fitsInCurrentPage(size_t size) {
+ return mNext && ((char*)mNext + size) <= end(mCurrentPage);
+}
+
+void LinearAllocator::ensureNext(size_t size) {
+ if (fitsInCurrentPage(size)) return;
+
+ if (mCurrentPage && mPageSize < MAX_PAGE_SIZE) {
+ mPageSize = min(MAX_PAGE_SIZE, mPageSize * 2);
+ mPageSize = ALIGN(mPageSize);
+ }
+ mWastedSpace += mPageSize;
+ Page* p = newPage(mPageSize);
+ if (mCurrentPage) {
+ mCurrentPage->setNext(p);
+ }
+ mCurrentPage = p;
+ if (!mPages) {
+ mPages = mCurrentPage;
+ }
+ mNext = start(mCurrentPage);
+}
+
+void* LinearAllocator::alloc(size_t size) {
+ size = ALIGN(size);
+ if (size > mMaxAllocSize && !fitsInCurrentPage(size)) {
+ ALOGV("Exceeded max size %zu > %zu", size, mMaxAllocSize);
+ // Allocation is too large, create a dedicated page for the allocation
+ Page* page = newPage(size);
+ mDedicatedPageCount++;
+ page->setNext(mPages);
+ mPages = page;
+ if (!mCurrentPage)
+ mCurrentPage = mPages;
+ return start(page);
+ }
+ ensureNext(size);
+ void* ptr = mNext;
+ mNext = ((char*)mNext) + size;
+ mWastedSpace -= size;
+ return ptr;
+}
+
+void LinearAllocator::addToDestructionList(Destructor dtor, void* addr) {
+ static_assert(std::is_standard_layout<DestructorNode>::value,
+ "DestructorNode must have standard layout");
+ static_assert(std::is_trivially_destructible<DestructorNode>::value,
+ "DestructorNode must be trivially destructable");
+ auto node = new (*this) DestructorNode();
+ node->dtor = dtor;
+ node->addr = addr;
+ node->next = mDtorList;
+ mDtorList = node;
+}
+
+void LinearAllocator::runDestructorFor(void* addr) {
+ auto node = mDtorList;
+ DestructorNode* previous = nullptr;
+ while (node) {
+ if (node->addr == addr) {
+ if (previous) {
+ previous->next = node->next;
+ } else {
+ mDtorList = node->next;
+ }
+ node->dtor(node->addr);
+ rewindIfLastAlloc(node, sizeof(DestructorNode));
+ break;
+ }
+ previous = node;
+ node = node->next;
+ }
+}
+
+void LinearAllocator::rewindIfLastAlloc(void* ptr, size_t allocSize) {
+ // First run the destructor as running the destructor will
+ // also rewind for the DestructorNode allocation which will
+ // have been allocated after this void* if it has a destructor
+ runDestructorFor(ptr);
+ // Don't bother rewinding across pages
+ allocSize = ALIGN(allocSize);
+ if (ptr >= start(mCurrentPage) && ptr < end(mCurrentPage)
+ && ptr == ((char*)mNext - allocSize)) {
+ mWastedSpace += allocSize;
+ mNext = ptr;
+ }
+}
+
+LinearAllocator::Page* LinearAllocator::newPage(size_t pageSize) {
+ pageSize = ALIGN(pageSize + sizeof(LinearAllocator::Page));
+ ADD_ALLOCATION(pageSize);
+ mTotalAllocated += pageSize;
+ mPageCount++;
+ void* buf = malloc(pageSize);
+ return new (buf) Page();
+}
+
+static const char* toSize(size_t value, float& result) {
+ if (value < 2000) {
+ result = value;
+ return "B";
+ }
+ if (value < 2000000) {
+ result = value / 1024.0f;
+ return "KB";
+ }
+ result = value / 1048576.0f;
+ return "MB";
+}
+
+void LinearAllocator::dumpMemoryStats(const char* prefix) {
+ float prettySize;
+ const char* prettySuffix;
+ prettySuffix = toSize(mTotalAllocated, prettySize);
+ ALOGD("%sTotal allocated: %.2f%s", prefix, prettySize, prettySuffix);
+ prettySuffix = toSize(mWastedSpace, prettySize);
+ ALOGD("%sWasted space: %.2f%s (%.1f%%)", prefix, prettySize, prettySuffix,
+ (float) mWastedSpace / (float) mTotalAllocated * 100.0f);
+ ALOGD("%sPages %zu (dedicated %zu)", prefix, mPageCount, mDedicatedPageCount);
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/utils/LinearAllocator.h b/libs/hwui/utils/LinearAllocator.h
new file mode 100644
index 0000000..d90dd82
--- /dev/null
+++ b/libs/hwui/utils/LinearAllocator.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2012, The Android Open Source Project
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef ANDROID_LINEARALLOCATOR_H
+#define ANDROID_LINEARALLOCATOR_H
+
+#include <stddef.h>
+#include <type_traits>
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * A memory manager that internally allocates multi-kbyte buffers for placing objects in. It avoids
+ * the overhead of malloc when many objects are allocated. It is most useful when creating many
+ * small objects with a similar lifetime, and doesn't add significant overhead for large
+ * allocations.
+ */
+class LinearAllocator {
+public:
+ LinearAllocator();
+ ~LinearAllocator();
+
+ /**
+ * Reserves and returns a region of memory of at least size 'size', aligning as needed.
+ * Typically this is used in an object's overridden new() method or as a replacement for malloc.
+ *
+ * The lifetime of the returned buffers is tied to that of the LinearAllocator. If calling
+ * delete() on an object stored in a buffer is needed, it should be overridden to use
+ * rewindIfLastAlloc()
+ */
+ void* alloc(size_t size);
+
+ /**
+ * Allocates an instance of the template type with the default constructor
+ * and adds it to the automatic destruction list.
+ */
+ template<class T>
+ T* alloc() {
+ T* ret = new (*this) T;
+ autoDestroy(ret);
+ return ret;
+ }
+
+ /**
+ * Adds the pointer to the tracking list to have its destructor called
+ * when the LinearAllocator is destroyed.
+ */
+ template<class T>
+ void autoDestroy(T* addr) {
+ if (!std::is_trivially_destructible<T>::value) {
+ auto dtor = [](void* addr) { ((T*)addr)->~T(); };
+ addToDestructionList(dtor, addr);
+ }
+ }
+
+ /**
+ * Attempt to deallocate the given buffer, with the LinearAllocator attempting to rewind its
+ * state if possible.
+ */
+ void rewindIfLastAlloc(void* ptr, size_t allocSize);
+
+ /**
+ * Same as rewindIfLastAlloc(void*, size_t)
+ */
+ template<class T>
+ void rewindIfLastAlloc(T* ptr) {
+ rewindIfLastAlloc((void*)ptr, sizeof(T));
+ }
+
+ /**
+ * Dump memory usage statistics to the log (allocated and wasted space)
+ */
+ void dumpMemoryStats(const char* prefix = "");
+
+ /**
+ * The number of bytes used for buffers allocated in the LinearAllocator (does not count space
+ * wasted)
+ */
+ size_t usedSize() const { return mTotalAllocated - mWastedSpace; }
+
+private:
+ LinearAllocator(const LinearAllocator& other);
+
+ class Page;
+ typedef void (*Destructor)(void* addr);
+ struct DestructorNode {
+ Destructor dtor;
+ void* addr;
+ DestructorNode* next = nullptr;
+ };
+
+ void addToDestructionList(Destructor, void* addr);
+ void runDestructorFor(void* addr);
+ Page* newPage(size_t pageSize);
+ bool fitsInCurrentPage(size_t size);
+ void ensureNext(size_t size);
+ void* start(Page *p);
+ void* end(Page* p);
+
+ size_t mPageSize;
+ size_t mMaxAllocSize;
+ void* mNext;
+ Page* mCurrentPage;
+ Page* mPages;
+ DestructorNode* mDtorList = nullptr;
+
+ // Memory usage tracking
+ size_t mTotalAllocated;
+ size_t mWastedSpace;
+ size_t mPageCount;
+ size_t mDedicatedPageCount;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+void* operator new(std::size_t size, android::uirenderer::LinearAllocator& la);
+
+#endif // ANDROID_LINEARALLOCATOR_H
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index 2346abe..201a796 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -59,7 +59,7 @@
/** Minimum value for sample rate */
private static final int SAMPLE_RATE_HZ_MIN = 4000;
/** Maximum value for sample rate */
- private static final int SAMPLE_RATE_HZ_MAX = 96000;
+ private static final int SAMPLE_RATE_HZ_MAX = 192000;
/**
* indicates AudioRecord state is not successfully initialized.
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index de18d06..3577357 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -96,7 +96,7 @@
/** Minimum value for sample rate */
private static final int SAMPLE_RATE_HZ_MIN = 4000;
/** Maximum value for sample rate */
- private static final int SAMPLE_RATE_HZ_MAX = 96000;
+ private static final int SAMPLE_RATE_HZ_MAX = 192000;
/** Maximum value for AudioTrack channel count */
private static final int CHANNEL_COUNT_MAX = 8;
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index f0e2f5c5..46d33b4 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -16,6 +16,7 @@
package android.media.tv;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.content.ComponentName;
import android.content.Context;
@@ -424,7 +425,7 @@
* @return a CharSequence containing the TV input's label. If the TV input does not have
* a label, its name is returned.
*/
- public CharSequence loadLabel(Context context) {
+ public CharSequence loadLabel(@NonNull Context context) {
if (TextUtils.isEmpty(mLabel)) {
return mService.loadLabel(context.getPackageManager());
} else {
@@ -452,7 +453,7 @@
* @return a Drawable containing the TV input's icon. If the TV input does not have an icon,
* application's icon is returned. If it's unavailable too, {@code null} is returned.
*/
- public Drawable loadIcon(Context context) {
+ public Drawable loadIcon(@NonNull Context context) {
if (mIconUri == null) {
return loadServiceIcon(context);
}
@@ -506,7 +507,7 @@
* @param flags The flags used for parceling.
*/
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mId);
dest.writeString(mParentId);
mService.writeToParcel(dest, flags);
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 105084e..0f265de 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -16,6 +16,7 @@
package android.media.tv;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.graphics.Rect;
@@ -932,7 +933,7 @@
* @return the {@link TvInputInfo} for a given TV input. {@code null} if not found.
*/
@Nullable
- public TvInputInfo getTvInputInfo(String inputId) {
+ public TvInputInfo getTvInputInfo(@NonNull String inputId) {
if (inputId == null) {
throw new IllegalArgumentException("inputId cannot be null");
}
@@ -956,7 +957,7 @@
* @param inputId The id of the TV input.
* @throws IllegalArgumentException if the argument is {@code null}.
*/
- public int getInputState(String inputId) {
+ public int getInputState(@NonNull String inputId) {
if (inputId == null) {
throw new IllegalArgumentException("inputId cannot be null");
}
@@ -977,7 +978,7 @@
* @param handler A {@link Handler} that the status change will be delivered to.
* @throws IllegalArgumentException if any of the arguments is {@code null}.
*/
- public void registerCallback(TvInputCallback callback, Handler handler) {
+ public void registerCallback(@NonNull TvInputCallback callback, @NonNull Handler handler) {
if (callback == null) {
throw new IllegalArgumentException("callback cannot be null");
}
@@ -995,7 +996,7 @@
* @param callback The existing callback to remove.
* @throws IllegalArgumentException if any of the arguments is {@code null}.
*/
- public void unregisterCallback(final TvInputCallback callback) {
+ public void unregisterCallback(@NonNull final TvInputCallback callback) {
if (callback == null) {
throw new IllegalArgumentException("callback cannot be null");
}
@@ -1047,7 +1048,7 @@
* @param rating The TV content rating to check.
* @return {@code true} if the given TV content rating is blocked, {@code false} otherwise.
*/
- public boolean isRatingBlocked(TvContentRating rating) {
+ public boolean isRatingBlocked(@NonNull TvContentRating rating) {
if (rating == null) {
throw new IllegalArgumentException("rating cannot be null");
}
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 382ec91..4258534 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -16,6 +16,7 @@
package android.media.tv;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
@@ -314,7 +315,7 @@
@SystemApi
public void notifySessionEvent(final String eventType, final Bundle eventArgs) {
if (eventType == null) {
- throw new IllegalArgumentException("eventType should not be null.");
+ throw new IllegalArgumentException("eventType cannot be null");
}
executeOrPostRunnable(new Runnable() {
@Override
@@ -544,7 +545,10 @@
* @see #notifyContentAllowed
* @see TvInputManager
*/
- public void notifyContentBlocked(final TvContentRating rating) {
+ public void notifyContentBlocked(@NonNull final TvContentRating rating) {
+ if (rating == null) {
+ throw new IllegalArgumentException("rating cannot be null");
+ }
executeOrPostRunnable(new Runnable() {
@Override
public void run() {
@@ -828,7 +832,7 @@
* @hide
*/
@SystemApi
- public void onAppPrivateCommand(String action, Bundle data) {
+ public void onAppPrivateCommand(@NonNull String action, Bundle data) {
}
/**
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 6456049..d248b12 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -16,6 +16,7 @@
package android.media.tv;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.content.Context;
@@ -273,7 +274,7 @@
* @param inputId The ID of TV input which will play the given channel.
* @param channelUri The URI of a channel.
*/
- public void tune(String inputId, Uri channelUri) {
+ public void tune(@NonNull String inputId, Uri channelUri) {
tune(inputId, channelUri, null);
}
@@ -494,7 +495,7 @@
* @hide
*/
@SystemApi
- public void sendAppPrivateCommand(String action, Bundle data) {
+ public void sendAppPrivateCommand(@NonNull String action, Bundle data) {
if (TextUtils.isEmpty(action)) {
throw new IllegalArgumentException("action cannot be null or an empty string");
}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
index 6f33672..d71b44b 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
@@ -19,6 +19,7 @@
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.ICameraDeviceCallbacks;
@@ -170,7 +171,8 @@
assertEquals(CameraBinderTestUtils.NO_ERROR, status);
assertFalse(metadata.isEmpty());
- CaptureRequest.Builder request = new CaptureRequest.Builder(metadata, /*reprocess*/false);
+ CaptureRequest.Builder request = new CaptureRequest.Builder(metadata, /*reprocess*/false,
+ CameraCaptureSession.SESSION_ID_NONE);
assertFalse(request.isEmpty());
assertFalse(metadata.isEmpty());
if (needStream) {
diff --git a/preloaded-classes b/preloaded-classes
index 86bd5c9..95d0b42 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -828,6 +828,11 @@
android.hardware.usb.UsbDevice
android.hardware.usb.UsbDeviceConnection
android.hardware.usb.UsbRequest
+# Initializing android.icu.impl.ICUBinary loads the ICU data.
+# Opening the files in the Zygote avoids StrictMode violations.
+# It also ensures the ICU data files are mapped on boot and all
+# apps will be consistent (even if files are added to /data).
+android.icu.impl.ICUBinary
android.inputmethodservice.ExtractEditText
android.location.Location
android.location.Location$1
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a48a4d9..4970e0f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5508,17 +5508,20 @@
if (app.isolated) {
mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid);
}
- app.kill(reason, true);
- handleAppDiedLocked(app, true, allowRestart);
- removeLruProcessLocked(app);
-
+ boolean willRestart = false;
if (app.persistent && !app.isolated) {
if (!callerWillRestart) {
- addAppLocked(app.info, false, null /* ABI override */);
+ willRestart = true;
} else {
needRestart = true;
}
}
+ app.kill(reason, true);
+ handleAppDiedLocked(app, willRestart, allowRestart);
+ if (willRestart) {
+ removeLruProcessLocked(app);
+ addAppLocked(app.info, false, null /* ABI override */);
+ }
} else {
mRemovedProcesses.add(app);
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 9a30f0d..6b56279 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -86,6 +86,8 @@
import android.provider.Settings.System;
import android.telecom.TelecomManager;
import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
import android.util.MathUtils;
import android.util.Slog;
@@ -110,10 +112,8 @@
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
-import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
-import java.util.Set;
/**
* The implementation of the volume manager service.
@@ -407,8 +407,7 @@
return "0x" + Integer.toHexString(device) + ":" + deviceAddress;
}
- private final HashMap<String, DeviceListSpec> mConnectedDevices =
- new HashMap<String, DeviceListSpec>();
+ private final ArrayMap<String, DeviceListSpec> mConnectedDevices = new ArrayMap<>();
// Forced device usage for communications
private int mForcedUseForComm;
@@ -2830,16 +2829,22 @@
}
}
public void onServiceDisconnected(int profile) {
+ ArraySet<String> toRemove = null;
switch (profile) {
case BluetoothProfile.A2DP:
synchronized (mConnectedDevices) {
synchronized (mA2dpAvrcpLock) {
// Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices
- for(Map.Entry<String, DeviceListSpec> entry
- : mConnectedDevices.entrySet()) {
- DeviceListSpec deviceSpec = entry.getValue();
+ for (int i = 0; i < mConnectedDevices.size(); i++) {
+ DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i);
if (deviceSpec.mDeviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
- makeA2dpDeviceUnavailableNow(deviceSpec.mDeviceAddress);
+ toRemove = toRemove != null ? toRemove : new ArraySet<String>();
+ toRemove.add(deviceSpec.mDeviceAddress);
+ }
+ }
+ if (toRemove != null) {
+ for (int i = 0; i < toRemove.size(); i++) {
+ makeA2dpDeviceUnavailableNow(toRemove.valueAt(i));
}
}
}
@@ -2849,11 +2854,16 @@
case BluetoothProfile.A2DP_SINK:
synchronized (mConnectedDevices) {
// Disconnect ALL DEVICE_IN_BLUETOOTH_A2DP devices
- for(Map.Entry<String, DeviceListSpec> entry
- : mConnectedDevices.entrySet()) {
- DeviceListSpec deviceSpec = entry.getValue();
+ for(int i = 0; i < mConnectedDevices.size(); i++) {
+ DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i);
if (deviceSpec.mDeviceType == AudioSystem.DEVICE_IN_BLUETOOTH_A2DP) {
- makeA2dpSrcUnavailable(deviceSpec.mDeviceAddress);
+ toRemove = toRemove != null ? toRemove : new ArraySet<String>();
+ toRemove.add(deviceSpec.mDeviceAddress);
+ }
+ }
+ if (toRemove != null) {
+ for (int i = 0; i < toRemove.size(); i++) {
+ makeA2dpSrcUnavailable(toRemove.valueAt(i));
}
}
}
@@ -4147,11 +4157,8 @@
// Restore device connection states
synchronized (mConnectedDevices) {
- Set set = mConnectedDevices.entrySet();
- Iterator i = set.iterator();
- while (i.hasNext()) {
- Map.Entry device = (Map.Entry)i.next();
- DeviceListSpec spec = (DeviceListSpec)device.getValue();
+ for (int i = 0; i < mConnectedDevices.size(); i++) {
+ DeviceListSpec spec = mConnectedDevices.valueAt(i);
AudioSystem.setDeviceConnectionState(
spec.mDeviceType,
AudioSystem.DEVICE_STATE_AVAILABLE,
@@ -4600,8 +4607,8 @@
int delay = 0;
if ((state == 0) && ((device & mBecomingNoisyIntentDevices) != 0)) {
int devices = 0;
- for (String key : mConnectedDevices.keySet()) {
- int dev = mConnectedDevices.get(key).mDeviceType;
+ for (int i = 0; i < mConnectedDevices.size(); i++) {
+ int dev = mConnectedDevices.valueAt(i).mDeviceType;
if (((dev & AudioSystem.DEVICE_BIT_IN) == 0)
&& ((dev & mBecomingNoisyIntentDevices) != 0)) {
devices |= dev;
diff --git a/services/core/java/com/android/server/pm/KeySetManagerService.java b/services/core/java/com/android/server/pm/KeySetManagerService.java
index db3ae91..c8e5c3a 100644
--- a/services/core/java/com/android/server/pm/KeySetManagerService.java
+++ b/services/core/java/com/android/server/pm/KeySetManagerService.java
@@ -488,7 +488,7 @@
}
public String encodePublicKey(PublicKey k) throws IOException {
- return new String(Base64.encode(k.getEncoded(), 0));
+ return new String(Base64.encode(k.getEncoded(), Base64.NO_WRAP));
}
public void dumpLPr(PrintWriter pw, String packageName,
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index e914cd4..a210223 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -281,7 +281,7 @@
Slog.w(WindowManagerService.TAG, "removeAllWindows: removing win=" + win);
}
- service.removeWindowLocked(win.mSession, win);
+ service.removeWindowLocked(win);
}
allAppWindows.clear();
windows.clear();
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index bb53534..c24fcb3 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -78,7 +78,7 @@
WindowState windowState = (WindowState) inputWindowHandle.windowState;
if (windowState != null) {
Slog.i(WindowManagerService.TAG, "WINDOW DIED " + windowState);
- mService.removeWindowLocked(windowState.mSession, windowState);
+ mService.removeWindowLocked(windowState);
}
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 38fc959..c5bdbb0 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2660,11 +2660,11 @@
if (win == null) {
return;
}
- removeWindowLocked(session, win);
+ removeWindowLocked(win);
}
}
- public void removeWindowLocked(Session session, WindowState win) {
+ void removeWindowLocked(WindowState win) {
if (win.mAttrs.type == TYPE_APPLICATION_STARTING) {
if (DEBUG_STARTING_WINDOW) Slog.d(TAG, "Starting window removed " + win);
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index cd9689e..ec70879 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1169,10 +1169,10 @@
WindowState win = mService.windowForClientLocked(mSession, mClient, false);
Slog.i(TAG, "WIN DEATH: " + win);
if (win != null) {
- mService.removeWindowLocked(mSession, win);
+ mService.removeWindowLocked(win);
} else if (mHasSurface) {
Slog.e(TAG, "!!! LEAK !!! Window removed but surface still valid.");
- mService.removeWindowLocked(mSession, WindowState.this);
+ mService.removeWindowLocked(WindowState.this);
}
}
} catch (IllegalArgumentException ex) {
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index bbeee8c..b303505 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -83,7 +83,7 @@
WindowState win = windows.get(winNdx);
if (WindowManagerService.DEBUG_WINDOW_MOVEMENT) Slog.w(WindowManagerService.TAG,
"removeAllWindows: removing win=" + win);
- win.mService.removeWindowLocked(win.mSession, win);
+ win.mService.removeWindowLocked(win);
}
windows.clear();
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index eb9234a..6fc3103 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3983,15 +3983,7 @@
+ " for device owner");
}
synchronized (this) {
- if (!allowedToSetDeviceOwnerOnDevice()) {
- throw new IllegalStateException(
- "Trying to set device owner but device is already provisioned.");
- }
-
- if (mDeviceOwner != null && mDeviceOwner.hasDeviceOwner()) {
- throw new IllegalStateException(
- "Trying to set device owner but device owner is already set.");
- }
+ enforceCanSetDeviceOwner();
// Shutting down backup manager service permanently.
long ident = Binder.clearCallingIdentity();
@@ -4009,7 +4001,7 @@
// Device owner is not set and does not exist, set it.
mDeviceOwner = DeviceOwner.createWithDeviceOwner(packageName, ownerName);
} else {
- // Device owner is not set but a profile owner exists, update Device owner state.
+ // Device owner state already exists, update it.
mDeviceOwner.setDeviceOwner(packageName, ownerName);
}
mDeviceOwner.writeOwnerFile();
@@ -4225,43 +4217,23 @@
if (!mHasFeature) {
return false;
}
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null);
-
- UserInfo info = mUserManager.getUserInfo(userHandle);
- if (info == null) {
- // User doesn't exist.
- throw new IllegalArgumentException(
- "Attempted to set profile owner for invalid userId: " + userHandle);
- }
- if (info.isGuest()) {
- throw new IllegalStateException("Cannot set a profile owner on a guest");
- }
-
if (who == null
|| !DeviceOwner.isInstalledForUser(who.getPackageName(), userHandle)) {
throw new IllegalArgumentException("Component " + who
+ " not installed for userId:" + userHandle);
}
synchronized (this) {
- // Only SYSTEM_UID can override the userSetupComplete
- if (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID
- && hasUserSetupCompleted(userHandle)) {
- throw new IllegalStateException(
- "Trying to set profile owner but user is already set-up.");
- }
-
+ enforceCanSetProfileOwner(userHandle);
if (mDeviceOwner == null) {
// Device owner state does not exist, create it.
mDeviceOwner = DeviceOwner.createWithProfileOwner(who, ownerName,
userHandle);
- mDeviceOwner.writeOwnerFile();
- return true;
} else {
- // Device owner already exists, update it.
+ // Device owner state already exists, update it.
mDeviceOwner.setProfileOwner(who, ownerName, userHandle);
- mDeviceOwner.writeOwnerFile();
- return true;
}
+ mDeviceOwner.writeOwnerFile();
+ return true;
}
}
@@ -4451,18 +4423,77 @@
}
/**
- * Device owner can only be set on an unprovisioned device. However, if initiated via "adb",
- * we also allow it if no accounts or additional users are present on the device.
+ * The profile owner can only be set by adb or an app with the MANAGE_PROFILE_AND_DEVICE_OWNERS
+ * permission.
+ * The profile owner can only be set before the user setup phase has completed,
+ * except for:
+ * - SYSTEM_UID
+ * - adb if there are not accounts.
*/
- private boolean allowedToSetDeviceOwnerOnDevice() {
- if (!hasUserSetupCompleted(UserHandle.USER_OWNER)) {
- return true;
+ private void enforceCanSetProfileOwner(int userHandle) {
+ UserInfo info = mUserManager.getUserInfo(userHandle);
+ if (info == null) {
+ // User doesn't exist.
+ throw new IllegalArgumentException(
+ "Attempted to set profile owner for invalid userId: " + userHandle);
}
+ if (info.isGuest()) {
+ throw new IllegalStateException("Cannot set a profile owner on a guest");
+ }
+ if (getProfileOwner(userHandle) != null) {
+ throw new IllegalStateException("Trying to set the profile owner, but profile owner "
+ + "is already set.");
+ }
+ int callingUid = Binder.getCallingUid();
+ if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) {
+ if (hasUserSetupCompleted(userHandle) &&
+ AccountManager.get(mContext).getAccountsAsUser(userHandle).length > 0) {
+ throw new IllegalStateException("Not allowed to set the profile owner because "
+ + "there are already some accounts on the profile");
+ }
+ return;
+ }
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS, null);
+ if (hasUserSetupCompleted(userHandle)
+ && UserHandle.getAppId(callingUid) != Process.SYSTEM_UID) {
+ throw new IllegalStateException("Cannot set the profile owner on a user which is "
+ + "already set-up");
+ }
+ }
- int callingId = Binder.getCallingUid();
- return (callingId == Process.SHELL_UID || callingId == Process.ROOT_UID)
- && mUserManager.getUserCount() == 1
- && AccountManager.get(mContext).getAccounts().length == 0;
+ /**
+ * The Device owner can only be set by adb or an app with the MANAGE_PROFILE_AND_DEVICE_OWNERS
+ * permission.
+ * The device owner can only be set before the setup phase of the primary user has completed,
+ * except for adb if no accounts or additional users are present on the device.
+ */
+ private void enforceCanSetDeviceOwner() {
+ if (mDeviceOwner != null && mDeviceOwner.hasDeviceOwner()) {
+ throw new IllegalStateException("Trying to set the device owner, but device owner "
+ + "is already set.");
+ }
+ int callingUid = Binder.getCallingUid();
+ if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) {
+ if (!hasUserSetupCompleted(UserHandle.USER_OWNER)) {
+ return;
+ }
+ if (mUserManager.getUserCount() > 1) {
+ throw new IllegalStateException("Not allowed to set the device owner because there "
+ + "are already several users on the device");
+ }
+ if (AccountManager.get(mContext).getAccounts().length > 0) {
+ throw new IllegalStateException("Not allowed to set the device owner because there "
+ + "are already some accounts on the device");
+ }
+ return;
+ }
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS, null);
+ if (hasUserSetupCompleted(UserHandle.USER_OWNER)) {
+ throw new IllegalStateException("Cannot set the device owner if the device is "
+ + "already set-up");
+ }
}
private void enforceCrossUserPermission(int userHandle) {
diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java
index 73bcd0c..3a7faf6 100644
--- a/telecomm/java/android/telecom/DisconnectCause.java
+++ b/telecomm/java/android/telecom/DisconnectCause.java
@@ -130,8 +130,10 @@
/**
* Returns a short label which explains the reason for the disconnect cause and is for display
- * in the user interface. The {@link ConnectionService } is responsible for providing and
- * localizing this label. If there is no string provided, returns null.
+ * in the user interface. If not null, it is expected that the In-Call UI should display this
+ * text where it would normally display the call state ("Dialing", "Disconnected") and is
+ * therefore expected to be relatively small. The {@link ConnectionService } is responsible for
+ * providing and localizing this label. If there is no string provided, returns null.
*
* @return The disconnect label.
*/
@@ -141,8 +143,11 @@
/**
* Returns a description which explains the reason for the disconnect cause and is for display
- * in the user interface. The {@link ConnectionService } is responsible for providing and
- * localizing this message. If there is no string provided, returns null.
+ * in the user interface. This optional text is generally a longer and more descriptive version
+ * of {@link #getLabel}, however it can exist even if {@link #getLabel} is empty. The In-Call UI
+ * should display this relatively prominently; the traditional implementation displays this as
+ * an alert dialog. The {@link ConnectionService} is responsible for providing and localizing
+ * this message. If there is no string provided, returns null.
*
* @return The disconnect description.
*/