summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/app/ActivityThread.java4
-rw-r--r--core/java/android/app/ActivityView.java45
-rw-r--r--core/java/android/app/BackStackRecord.java8
-rw-r--r--core/java/android/app/PendingIntent.java14
-rw-r--r--core/java/android/app/usage/NetworkStats.java4
-rw-r--r--core/java/android/content/ContentResolver.java14
-rw-r--r--core/java/android/hardware/camera2/CameraCharacteristics.java33
-rw-r--r--core/java/android/hardware/camera2/CameraManager.java18
-rw-r--r--core/java/android/hardware/camera2/CaptureRequest.java46
-rw-r--r--core/java/android/hardware/camera2/CaptureResult.java33
-rw-r--r--core/java/android/hardware/camera2/ICameraDeviceUser.aidl2
-rw-r--r--core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java44
-rw-r--r--core/java/android/hardware/camera2/impl/CameraDeviceImpl.java190
-rw-r--r--core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java2
-rw-r--r--core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java37
-rw-r--r--core/java/android/hardware/camera2/utils/SurfaceUtils.java80
-rw-r--r--core/java/android/inputmethodservice/ExtractEditText.java2
-rw-r--r--core/java/android/os/DropBoxManager.java3
-rw-r--r--core/java/android/os/ParcelFileDescriptor.java10
-rw-r--r--core/java/android/provider/AlarmClock.java121
-rw-r--r--core/java/android/security/IKeystoreService.aidl3
-rw-r--r--core/java/android/util/DisplayMetrics.java8
-rw-r--r--core/java/android/util/StateSet.java7
-rw-r--r--core/java/android/view/ScaleGestureDetector.java4
-rw-r--r--core/java/android/view/View.java1
-rw-r--r--core/java/android/view/WindowInsets.java2
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java5
-rw-r--r--core/java/android/widget/AbsListView.java98
-rw-r--r--core/java/android/widget/AdapterView.java59
-rw-r--r--core/java/android/widget/Editor.java306
-rw-r--r--core/java/android/widget/TextView.java89
-rw-r--r--core/java/com/android/internal/view/FloatingActionMode.java13
-rw-r--r--core/java/com/android/internal/widget/FloatingToolbar.java18
33 files changed, 934 insertions, 389 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 3224d41..10d76f7 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1640,6 +1640,10 @@ public final class ActivityThread {
return sCurrentActivityThread;
}
+ public static boolean isSystem() {
+ return (sCurrentActivityThread != null) ? sCurrentActivityThread.mSystemThread : false;
+ }
+
public static String currentOpPackageName() {
ActivityThread am = currentActivityThread();
return (am != null && am.getApplication() != null)
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index eafcdb2..9c0d931 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -24,7 +24,10 @@ import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentSender;
import android.graphics.SurfaceTexture;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.Message;
import android.os.OperationCanceledException;
import android.os.RemoteException;
import android.util.AttributeSet;
@@ -48,7 +51,9 @@ public class ActivityView extends ViewGroup {
private static final String TAG = "ActivityView";
private static final boolean DEBUG = false;
- DisplayMetrics mMetrics;
+ private static final int MSG_SET_SURFACE = 1;
+
+ DisplayMetrics mMetrics = new DisplayMetrics();
private final TextureView mTextureView;
private ActivityContainerWrapper mActivityContainer;
private Activity mActivity;
@@ -58,6 +63,9 @@ public class ActivityView extends ViewGroup {
private int mLastVisibility;
private ActivityViewCallback mActivityViewCallback;
+ private HandlerThread mThread = new HandlerThread("ActivityViewThread");
+ private Handler mHandler;
+
public ActivityView(Context context) {
this(context, null);
}
@@ -89,12 +97,27 @@ public class ActivityView extends ViewGroup {
+ e);
}
+ mThread.start();
+ mHandler = new Handler(mThread.getLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ super.handleMessage(msg);
+ if (msg.what == MSG_SET_SURFACE) {
+ try {
+ mActivityContainer.setSurface((Surface) msg.obj, msg.arg1, msg.arg2,
+ mMetrics.densityDpi);
+ } catch (RemoteException e) {
+ throw new RuntimeException(
+ "ActivityView: Unable to set surface of ActivityContainer. " + e);
+ }
+ }
+ }
+ };
mTextureView = new TextureView(context);
mTextureView.setSurfaceTextureListener(new ActivityViewSurfaceTextureListener());
addView(mTextureView);
WindowManager wm = (WindowManager)mActivity.getSystemService(Context.WINDOW_SERVICE);
- mMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(mMetrics);
mLastVisibility = getVisibility();
@@ -111,18 +134,12 @@ public class ActivityView extends ViewGroup {
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
- if (mSurface != null) {
- try {
- if (visibility == View.GONE) {
- mActivityContainer.setSurface(null, mWidth, mHeight, mMetrics.densityDpi);
- } else if (mLastVisibility == View.GONE) {
- // Don't change surface when going between View.VISIBLE and View.INVISIBLE.
- mActivityContainer.setSurface(mSurface, mWidth, mHeight, mMetrics.densityDpi);
- }
- } catch (RemoteException e) {
- throw new RuntimeException(
- "ActivityView: Unable to set surface of ActivityContainer. " + e);
- }
+ if (mSurface != null && (visibility == View.GONE || mLastVisibility == View.GONE)) {
+ Message msg = Message.obtain(mHandler, MSG_SET_SURFACE);
+ msg.obj = (visibility == View.GONE) ? null : mSurface;
+ msg.arg1 = mWidth;
+ msg.arg2 = mHeight;
+ mHandler.sendMessage(msg);
}
mLastVisibility = visibility;
}
diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java
index 1fb88a9..02e26a5 100644
--- a/core/java/android/app/BackStackRecord.java
+++ b/core/java/android/app/BackStackRecord.java
@@ -1107,6 +1107,7 @@ final class BackStackRecord extends FragmentTransaction implements
}
if (enterTransition != null) {
+ enterTransition.removeTarget(state.nonExistentView);
View view = inFragment.getView();
if (view != null) {
view.captureTransitioningViews(enteringViews);
@@ -1115,7 +1116,6 @@ final class BackStackRecord extends FragmentTransaction implements
}
enteringViews.add(state.nonExistentView);
// We added this earlier to prevent any views being targeted.
- enterTransition.removeTarget(state.nonExistentView);
addTargets(enterTransition, enteringViews);
}
setSharedElementEpicenter(enterTransition, state);
@@ -1170,7 +1170,7 @@ final class BackStackRecord extends FragmentTransaction implements
Transition exitTransition, Transition sharedElementTransition, Fragment inFragment,
boolean isBack) {
boolean overlap = true;
- if (enterTransition != null && exitTransition != null) {
+ if (enterTransition != null && exitTransition != null && inFragment != null) {
overlap = isBack ? inFragment.getAllowReturnTransitionOverlap() :
inFragment.getAllowEnterTransitionOverlap();
}
@@ -1638,7 +1638,7 @@ final class BackStackRecord extends FragmentTransaction implements
private void setBackNameOverrides(TransitionState state, ArrayMap<String, View> namedViews,
boolean isEnd) {
- int count = mSharedElementTargetNames.size();
+ int count = mSharedElementTargetNames == null ? 0 : mSharedElementTargetNames.size();
for (int i = 0; i < count; i++) {
String source = mSharedElementSourceNames.get(i);
String originalTarget = mSharedElementTargetNames.get(i);
@@ -1656,7 +1656,7 @@ final class BackStackRecord extends FragmentTransaction implements
private void setNameOverrides(TransitionState state, ArrayMap<String, View> namedViews,
boolean isEnd) {
- int count = namedViews.size();
+ int count = namedViews == null ? 0 : namedViews.size();
for (int i = 0; i < count; i++) {
String source = namedViews.keyAt(i);
String target = namedViews.valueAt(i).getTransitionName();
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 2cfc1fa4..031854a 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -25,11 +25,13 @@ import android.content.IIntentReceiver;
import android.content.IIntentSender;
import android.content.IntentSender;
import android.os.Bundle;
+import android.os.Looper;
import android.os.RemoteException;
import android.os.Handler;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.Process;
import android.os.UserHandle;
import android.util.AndroidException;
@@ -206,10 +208,20 @@ public final class PendingIntent implements Parcelable {
private int mResultCode;
private String mResultData;
private Bundle mResultExtras;
+ private static Handler sDefaultSystemHandler;
FinishedDispatcher(PendingIntent pi, OnFinished who, Handler handler) {
mPendingIntent = pi;
mWho = who;
- mHandler = handler;
+ if (handler == null && ActivityThread.isSystem()) {
+ // We assign a default handler for the system process to avoid deadlocks when
+ // processing receivers in various components that hold global service locks.
+ if (sDefaultSystemHandler == null) {
+ sDefaultSystemHandler = new Handler(Looper.getMainLooper());
+ }
+ mHandler = sDefaultSystemHandler;
+ } else {
+ mHandler = handler;
+ }
}
public void performReceive(Intent intent, int resultCode, String data,
Bundle extras, boolean serialized, boolean sticky, int sendingUser) {
diff --git a/core/java/android/app/usage/NetworkStats.java b/core/java/android/app/usage/NetworkStats.java
index 5193563..1079f1a 100644
--- a/core/java/android/app/usage/NetworkStats.java
+++ b/core/java/android/app/usage/NetworkStats.java
@@ -29,11 +29,11 @@ import android.util.Log;
import dalvik.system.CloseGuard;
/**
- * Class providing enumeration over buckets of network usage statistics. NetworkUsageStats objects
+ * Class providing enumeration over buckets of network usage statistics. {@link NetworkStats} objects
* are returned as results to various queries in {@link NetworkStatsManager}.
*/
public final class NetworkStats implements AutoCloseable {
- private final static String TAG = "NetworkUsageStats";
+ private final static String TAG = "NetworkStats";
private final CloseGuard mCloseGuard = CloseGuard.get();
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 17a8eb7..96a80e7 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -1552,23 +1552,21 @@ public abstract class ContentResolver {
*
* @param uri The URI to watch for changes. This can be a specific row URI, or a base URI
* for a whole class of content.
- * @param notifyForDescendents If <code>true</code> changes to URIs beginning with <code>uri</code>
- * will also cause notifications to be sent. If <code>false</code> only changes to the exact URI
- * specified by <em>uri</em> will cause notifications to be sent. If <code>true</code>, any URI values
- * at or below the specified URI will also trigger a match.
+ * @param notifyForDescendents When false, the observer will be notified whenever a
+ * change occurs to the exact URI specified by <code>uri</code> or to one of the
+ * URI's ancestors in the path hierarchy. When true, the observer will also be notified
+ * whenever a change occurs to the URI's descendants in the path hierarchy.
* @param observer The object that receives callbacks when changes occur.
* @see #unregisterContentObserver
*/
public final void registerContentObserver(Uri uri, boolean notifyForDescendents,
- ContentObserver observer)
- {
+ ContentObserver observer) {
registerContentObserver(uri, notifyForDescendents, observer, UserHandle.myUserId());
}
/** @hide - designated user version */
public final void registerContentObserver(Uri uri, boolean notifyForDescendents,
- ContentObserver observer, int userHandle)
- {
+ ContentObserver observer, int userHandle) {
try {
getContentService().registerContentObserver(uri, notifyForDescendents,
observer.getContentObserver(), userHandle);
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 85e8827..b69ca88 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1022,12 +1022,33 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
/**
* <p>Position of the camera optical center.</p>
- * <p>As measured in the device sensor coordinate system, the
- * position of the camera device's optical center, as a
- * three-dimensional vector <code>(x,y,z)</code>.</p>
- * <p>To transform a world position to a camera-device centered
- * coordinate system, the position must be translated by this
- * vector and then rotated by {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}.</p>
+ * <p>The position of the camera device's lens optical center,
+ * as a three-dimensional vector <code>(x,y,z)</code>, relative to the
+ * optical center of the largest camera device facing in the
+ * same direction as this camera, in the {@link android.hardware.SensorEvent Android sensor coordinate
+ * axes}. Note that only the axis definitions are shared with
+ * the sensor coordinate system, but not the origin.</p>
+ * <p>If this device is the largest or only camera device with a
+ * given facing, then this position will be <code>(0, 0, 0)</code>; a
+ * camera device with a lens optical center located 3 cm from
+ * the main sensor along the +X axis (to the right from the
+ * user's perspective) will report <code>(0.03, 0, 0)</code>.</p>
+ * <p>To transform a pixel coordinates between two cameras
+ * facing the same direction, first the source camera
+ * android.lens.radialDistortion must be corrected for. Then
+ * the source camera android.lens.intrinsicCalibration needs
+ * to be applied, followed by the {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}
+ * of the source camera, the translation of the source camera
+ * relative to the destination camera, the
+ * {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation} of the destination camera, and
+ * finally the inverse of android.lens.intrinsicCalibration
+ * of the destination camera. This obtains a
+ * radial-distortion-free coordinate in the destination
+ * camera pixel coordinates.</p>
+ * <p>To compare this against a real image from the destination
+ * camera, the destination camera image then needs to be
+ * corrected for radial distortion before comparison or
+ * sampling.</p>
* <p><b>Units</b>: Meters</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
*
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 0f002a9..0fb6889 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -802,12 +802,9 @@ public final class CameraManager {
*/
public ICameraService getCameraService() {
synchronized(mLock) {
+ connectCameraServiceLocked();
if (mCameraService == null) {
- Log.i(TAG, "getCameraService: Reconnecting to camera service");
- connectCameraServiceLocked();
- if (mCameraService == null) {
- Log.e(TAG, "Camera service is unavailable");
- }
+ Log.e(TAG, "Camera service is unavailable");
}
return mCameraService;
}
@@ -815,11 +812,16 @@ public final class CameraManager {
/**
* Connect to the camera service if it's available, and set up listeners.
+ * If the service is already connected, do nothing.
*
* <p>Sets mCameraService to a valid pointer or null if the connection does not succeed.</p>
*/
private void connectCameraServiceLocked() {
- mCameraService = null;
+ // Only reconnect if necessary
+ if (mCameraService != null) return;
+
+ Log.i(TAG, "Connecting to camera service");
+
IBinder cameraServiceBinder = ServiceManager.getService(CAMERA_SERVICE_BINDER_NAME);
if (cameraServiceBinder == null) {
// Camera service is now down, leave mCameraService as null
@@ -1098,6 +1100,8 @@ public final class CameraManager {
*/
public void registerAvailabilityCallback(AvailabilityCallback callback, Handler handler) {
synchronized (mLock) {
+ connectCameraServiceLocked();
+
Handler oldHandler = mCallbackMap.put(callback, handler);
// For new callbacks, provide initial availability information
if (oldHandler == null) {
@@ -1120,6 +1124,8 @@ public final class CameraManager {
public void registerTorchCallback(TorchCallback callback, Handler handler) {
synchronized(mLock) {
+ connectCameraServiceLocked();
+
Handler oldHandler = mTorchCallbackMap.put(callback, handler);
// For new callbacks, provide initial torch information
if (oldHandler == null) {
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index b2c1c71..bc625dd 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -169,6 +169,9 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
private final HashSet<Surface> mSurfaceSet;
private final CameraMetadataNative mSettings;
private boolean mIsReprocess;
+ // If this request is part of constrained high speed request list that was created by
+ // {@link CameraDevice#createConstrainedHighSpeedRequestList}.
+ private boolean mIsPartOfCHSRequestList = false;
// Each reprocess request must be tied to a reprocessable session ID.
// Valid only for reprocess requests (mIsReprocess == true).
private int mReprocessableSessionId;
@@ -197,6 +200,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
mSettings = new CameraMetadataNative(source.mSettings);
mSurfaceSet = (HashSet<Surface>) source.mSurfaceSet.clone();
mIsReprocess = source.mIsReprocess;
+ mIsPartOfCHSRequestList = source.mIsPartOfCHSRequestList;
mReprocessableSessionId = source.mReprocessableSessionId;
mUserTag = source.mUserTag;
}
@@ -321,6 +325,35 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
}
/**
+ * <p>Determine if this request is part of a constrained high speed request list that was
+ * created by {@link CameraDevice#createConstrainedHighSpeedRequestList}. A constrained high
+ * speed request list contains some constrained high speed capture requests with certain
+ * interleaved pattern that is suitable for high speed preview/video streaming. An active
+ * constrained high speed capture session only accepts constrained high speed request lists.
+ * This method can be used to do the sanity check when a constrained high speed capture session
+ * receives a request list via {@link CameraCaptureSession#setRepeatingBurst} or
+ * {@link CameraCaptureSession#captureBurst}.
+ * </p>
+ *
+ *
+ * @return {@code true} if this request is part of a constrained high speed request list,
+ * {@code false} otherwise.
+ *
+ * @hide
+ */
+ public boolean isPartOfCRequestList() {
+ return mIsPartOfCHSRequestList;
+ }
+
+ /**
+ * Returns a copy of the underlying {@link CameraMetadataNative}.
+ * @hide
+ */
+ public CameraMetadataNative getNativeCopy() {
+ return new CameraMetadataNative(mSettings);
+ }
+
+ /**
* Get the reprocessable session ID this reprocess capture request is associated with.
*
* @return the reprocessable session ID this reprocess capture request is associated with
@@ -547,6 +580,18 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
}
/**
+ * <p>Mark this request as part of a constrained high speed request list created by
+ * {@link CameraDevice#createConstrainedHighSpeedRequestList}. A constrained high speed
+ * request list contains some constrained high speed capture requests with certain
+ * interleaved pattern that is suitable for high speed preview/video streaming.</p>
+ *
+ * @hide
+ */
+ public void setPartOfCHSRequestList(boolean partOfCHSList) {
+ mRequest.mIsPartOfCHSRequestList = partOfCHSList;
+ }
+
+ /**
* Build a request using the current target Surfaces and settings.
* <p>Note that, although it is possible to create a {@code CaptureRequest} with no target
* {@link Surface}s, passing such a request into {@link CameraCaptureSession#capture},
@@ -563,7 +608,6 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
return new CaptureRequest(mRequest);
}
-
/**
* @hide
*/
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index df6c986..3bb2fdb 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2584,12 +2584,33 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
/**
* <p>Position of the camera optical center.</p>
- * <p>As measured in the device sensor coordinate system, the
- * position of the camera device's optical center, as a
- * three-dimensional vector <code>(x,y,z)</code>.</p>
- * <p>To transform a world position to a camera-device centered
- * coordinate system, the position must be translated by this
- * vector and then rotated by {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}.</p>
+ * <p>The position of the camera device's lens optical center,
+ * as a three-dimensional vector <code>(x,y,z)</code>, relative to the
+ * optical center of the largest camera device facing in the
+ * same direction as this camera, in the {@link android.hardware.SensorEvent Android sensor coordinate
+ * axes}. Note that only the axis definitions are shared with
+ * the sensor coordinate system, but not the origin.</p>
+ * <p>If this device is the largest or only camera device with a
+ * given facing, then this position will be <code>(0, 0, 0)</code>; a
+ * camera device with a lens optical center located 3 cm from
+ * the main sensor along the +X axis (to the right from the
+ * user's perspective) will report <code>(0.03, 0, 0)</code>.</p>
+ * <p>To transform a pixel coordinates between two cameras
+ * facing the same direction, first the source camera
+ * android.lens.radialDistortion must be corrected for. Then
+ * the source camera android.lens.intrinsicCalibration needs
+ * to be applied, followed by the {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}
+ * of the source camera, the translation of the source camera
+ * relative to the destination camera, the
+ * {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation} of the destination camera, and
+ * finally the inverse of android.lens.intrinsicCalibration
+ * of the destination camera. This obtains a
+ * radial-distortion-free coordinate in the destination
+ * camera pixel coordinates.</p>
+ * <p>To compare this against a real image from the destination
+ * camera, the destination camera image then needs to be
+ * corrected for radial distortion before comparison or
+ * sampling.</p>
* <p><b>Units</b>: Meters</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
*
diff --git a/core/java/android/hardware/camera2/ICameraDeviceUser.aidl b/core/java/android/hardware/camera2/ICameraDeviceUser.aidl
index 375b310..1574f93 100644
--- a/core/java/android/hardware/camera2/ICameraDeviceUser.aidl
+++ b/core/java/android/hardware/camera2/ICameraDeviceUser.aidl
@@ -61,7 +61,7 @@ interface ICameraDeviceUser
* must be called before any requests can be submitted.
* <p>
*/
- int endConfigure();
+ int endConfigure(boolean isConstrainedHighSpeed);
int deleteStream(int streamId);
diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
index ab0f607..cbc85f3 100644
--- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
@@ -60,6 +60,7 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession {
private final android.hardware.camera2.impl.CameraDeviceImpl mDeviceImpl;
/** Internal handler; used for all incoming events to preserve total order */
private final Handler mDeviceHandler;
+ private final boolean mIsConstrainedHighSpeedSession;
/** Drain Sequence IDs which have been queued but not yet finished with aborted/completed */
private final TaskDrainer<Integer> mSequenceDrainer;
@@ -88,13 +89,14 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession {
CameraCaptureSessionImpl(int id, Surface input, List<Surface> outputs,
CameraCaptureSession.StateCallback callback, Handler stateHandler,
android.hardware.camera2.impl.CameraDeviceImpl deviceImpl,
- Handler deviceStateHandler, boolean configureSuccess) {
+ Handler deviceStateHandler, boolean configureSuccess, boolean isConstrainedHighSpeed) {
if (outputs == null || outputs.isEmpty()) {
throw new IllegalArgumentException("outputs must be a non-null, non-empty list");
} else if (callback == null) {
throw new IllegalArgumentException("callback must not be null");
}
+ mIsConstrainedHighSpeedSession = isConstrainedHighSpeed;
mId = id;
mIdString = String.format("Session %d: ", mId);
@@ -134,6 +136,30 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession {
}
}
+
+ private boolean isConstrainedHighSpeedRequestList(List<CaptureRequest> requestList) {
+ checkCollectionNotEmpty(requestList, "High speed request list");
+ for (CaptureRequest request : requestList) {
+ if (!request.isPartOfCRequestList()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * If the session is constrained high speed session, it only accept constrained high speed
+ * request list.
+ */
+ private void checkConstrainedHighSpeedRequestSanity(List<CaptureRequest> requestList) {
+ if (mIsConstrainedHighSpeedSession) {
+ if (!isConstrainedHighSpeedRequestList(requestList)) {
+ throw new IllegalArgumentException("It is only allowed to submit a constrained "
+ + "high speed request list to a constrianed high speed session!!!");
+ }
+ }
+ }
+
@Override
public CameraDevice getDevice() {
return mDeviceImpl;
@@ -155,6 +181,10 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession {
} else if (request.isReprocess() && request.getReprocessableSessionId() != mId) {
throw new IllegalArgumentException("capture request was created for another session");
}
+ if (mIsConstrainedHighSpeedSession) {
+ throw new UnsupportedOperationException("Constrained high speed session doesn't support"
+ + " this method");
+ }
checkNotClosed();
@@ -178,6 +208,8 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession {
throw new IllegalArgumentException("Requests must have at least one element");
}
+ checkConstrainedHighSpeedRequestSanity(requests);
+
for (CaptureRequest request : requests) {
if (request.isReprocess()) {
if (!isReprocessable()) {
@@ -212,7 +244,10 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession {
} else if (request.isReprocess()) {
throw new IllegalArgumentException("repeating reprocess requests are not supported");
}
-
+ if (mIsConstrainedHighSpeedSession) {
+ throw new UnsupportedOperationException("Constrained high speed session doesn't support"
+ + " this method");
+ }
checkNotClosed();
@@ -236,6 +271,8 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession {
throw new IllegalArgumentException("requests must have at least one element");
}
+ checkConstrainedHighSpeedRequestSanity(requests);
+
for (CaptureRequest r : requests) {
if (r.isReprocess()) {
throw new IllegalArgumentException("repeating reprocess burst requests are not " +
@@ -704,7 +741,8 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession {
// everything is idle.
try {
// begin transition to unconfigured
- mDeviceImpl.configureStreamsChecked(null, null);
+ mDeviceImpl.configureStreamsChecked(/*inputConfig*/null, /*outputs*/null,
+ /*isConstrainedHighSpeed*/false);
} catch (CameraAccessException e) {
// OK: do not throw checked exceptions.
Log.e(TAG, mIdString + "Exception while unconfiguring outputs: ", e);
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 16701e5..c073ba5 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -18,6 +18,7 @@ package android.hardware.camera2.impl;
import static android.hardware.camera2.CameraAccessException.CAMERA_IN_USE;
+import android.graphics.ImageFormat;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
@@ -35,17 +36,22 @@ import android.hardware.camera2.params.StreamConfigurationMap;
import android.hardware.camera2.utils.CameraBinderDecorator;
import android.hardware.camera2.utils.CameraRuntimeException;
import android.hardware.camera2.utils.LongParcelable;
+import android.hardware.camera2.utils.SurfaceUtils;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
+import android.util.Range;
import android.util.Size;
import android.util.SparseArray;
import android.view.Surface;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -326,7 +332,8 @@ public class CameraDeviceImpl extends CameraDevice {
for (Surface s : outputs) {
outputConfigs.add(new OutputConfiguration(s));
}
- configureStreamsChecked(/*inputConfig*/null, outputConfigs);
+ configureStreamsChecked(/*inputConfig*/null, outputConfigs,
+ /*isConstrainedHighSpeed*/false);
}
@@ -344,12 +351,14 @@ public class CameraDeviceImpl extends CameraDevice {
*
* @param inputConfig input configuration or {@code null} for no input
* @param outputs a list of one or more surfaces, or {@code null} to unconfigure
+ * @param isConstrainedHighSpeed If the streams configuration is for constrained high speed output.
* @return whether or not the configuration was successful
*
* @throws CameraAccessException if there were any unexpected problems during configuration
*/
public boolean configureStreamsChecked(InputConfiguration inputConfig,
- List<OutputConfiguration> outputs) throws CameraAccessException {
+ List<OutputConfiguration> outputs, boolean isConstrainedHighSpeed)
+ throws CameraAccessException {
// Treat a null input the same an empty list
if (outputs == null) {
outputs = new ArrayList<OutputConfiguration>();
@@ -422,7 +431,7 @@ public class CameraDeviceImpl extends CameraDevice {
}
try {
- mRemoteDevice.endConfigure();
+ mRemoteDevice.endConfigure(isConstrainedHighSpeed);
}
catch (IllegalArgumentException e) {
// OK. camera service can reject stream config if it's not supported by HAL
@@ -463,7 +472,8 @@ public class CameraDeviceImpl extends CameraDevice {
for (Surface surface : outputs) {
outConfigurations.add(new OutputConfiguration(surface));
}
- createCaptureSessionInternal(null, outConfigurations, callback, handler);
+ createCaptureSessionInternal(null, outConfigurations, callback, handler,
+ /*isConstrainedHighSpeed*/false);
}
@Override
@@ -475,7 +485,8 @@ public class CameraDeviceImpl extends CameraDevice {
Log.d(TAG, "createCaptureSessionByOutputConfiguration");
}
- createCaptureSessionInternal(null, outputConfigurations, callback, handler);
+ createCaptureSessionInternal(null, outputConfigurations, callback, handler,
+ /*isConstrainedHighSpeed*/false);
}
@Override
@@ -494,13 +505,14 @@ public class CameraDeviceImpl extends CameraDevice {
for (Surface surface : outputs) {
outConfigurations.add(new OutputConfiguration(surface));
}
- createCaptureSessionInternal(inputConfig, outConfigurations, callback, handler);
+ createCaptureSessionInternal(inputConfig, outConfigurations, callback, handler,
+ /*isConstrainedHighSpeed*/false);
}
private void createCaptureSessionInternal(InputConfiguration inputConfig,
List<OutputConfiguration> outputConfigurations,
- CameraCaptureSession.StateCallback callback, Handler handler)
- throws CameraAccessException {
+ CameraCaptureSession.StateCallback callback, Handler handler,
+ boolean isConstrainedHighSpeed) throws CameraAccessException {
synchronized(mInterfaceLock) {
if (DEBUG) {
Log.d(TAG, "createCaptureSessionInternal");
@@ -508,6 +520,11 @@ public class CameraDeviceImpl extends CameraDevice {
checkIfCameraClosedOrInError();
+ if (isConstrainedHighSpeed && inputConfig != null) {
+ throw new IllegalArgumentException("Constrained high speed session doesn't support"
+ + " input configuration yet.");
+ }
+
// Notify current session that it's going away, before starting camera operations
// After this call completes, the session is not allowed to call into CameraDeviceImpl
if (mCurrentSession != null) {
@@ -520,7 +537,8 @@ public class CameraDeviceImpl extends CameraDevice {
Surface input = null;
try {
// configure streams and then block until IDLE
- configureSuccess = configureStreamsChecked(inputConfig, outputConfigurations);
+ configureSuccess = configureStreamsChecked(inputConfig, outputConfigurations,
+ isConstrainedHighSpeed);
if (inputConfig != null) {
input = new Surface();
mRemoteDevice.getInputSurface(/*out*/input);
@@ -545,7 +563,7 @@ public class CameraDeviceImpl extends CameraDevice {
CameraCaptureSessionImpl newSession =
new CameraCaptureSessionImpl(mNextSessionId++, input,
outSurfaces, callback, handler, this, mDeviceHandler,
- configureSuccess);
+ configureSuccess, isConstrainedHighSpeed);
// TODO: wait until current session closes, then create the new session
mCurrentSession = newSession;
@@ -906,7 +924,6 @@ public class CameraDeviceImpl extends CameraDevice {
}
mRemoteDevice = null;
- mInError = false;
}
}
@@ -1889,13 +1906,13 @@ public class CameraDeviceImpl extends CameraDevice {
}
private void checkIfCameraClosedOrInError() throws CameraAccessException {
+ if (mRemoteDevice == null) {
+ throw new IllegalStateException("CameraDevice was already closed");
+ }
if (mInError) {
throw new CameraAccessException(CameraAccessException.CAMERA_ERROR,
"The camera device has encountered a serious error");
}
- if (mRemoteDevice == null) {
- throw new IllegalStateException("CameraDevice was already closed");
- }
}
/** Whether the camera device has started to close (may not yet have finished) */
@@ -1907,17 +1924,156 @@ public class CameraDeviceImpl extends CameraDevice {
return mCharacteristics;
}
+ private void checkConstrainedHighSpeedSurfaces(Collection<Surface> surfaces,
+ Range<Integer> fpsRange) {
+ if (surfaces == null || surfaces.size() == 0 || surfaces.size() > 2) {
+ throw new IllegalArgumentException("Output target surface list must not be null and"
+ + " the size must be 1 or 2");
+ }
+
+ StreamConfigurationMap config =
+ getCharacteristics().get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+ List<Size> highSpeedSizes = null;
+ if (fpsRange == null) {
+ highSpeedSizes = Arrays.asList(config.getHighSpeedVideoSizes());
+ } else {
+ // Check the FPS range first if provided
+ Range<Integer>[] highSpeedFpsRanges = config.getHighSpeedVideoFpsRanges();
+ if(!Arrays.asList(highSpeedFpsRanges).contains(fpsRange)) {
+ throw new IllegalArgumentException("Fps range " + fpsRange.toString() + " in the"
+ + " request is not a supported high speed fps range " +
+ Arrays.toString(highSpeedFpsRanges));
+ }
+ highSpeedSizes = Arrays.asList(config.getHighSpeedVideoSizesFor(fpsRange));
+ }
+
+ for (Surface surface : surfaces) {
+ // Surface size must be supported high speed sizes.
+ Size surfaceSize = SurfaceUtils.getSurfaceSize(surface);
+ int surfaceFormat = SurfaceUtils.getSurfaceFormat(surface);
+
+ if (surfaceFormat != ImageFormat.PRIVATE) {
+ throw new IllegalArgumentException("Surface format is not for preview or"
+ + " hardware video encoding" + surfaceFormat);
+ }
+
+ if (!highSpeedSizes.contains(surfaceSize)) {
+ throw new IllegalArgumentException("Surface size " + surfaceSize.toString() + " is"
+ + " not part of the high speed supported size list " +
+ Arrays.toString(highSpeedSizes.toArray()));
+ }
+ // Each output surface must be either preview surface or recording surface.
+ if (!SurfaceUtils.isSurfaceForPreview(surface) &&
+ !SurfaceUtils.isSurfaceForHwVideoEncoder(surface)) {
+ throw new IllegalArgumentException("This output surface is neither preview nor "
+ + "hardware video encoding surface");
+ }
+ if (SurfaceUtils.isSurfaceForPreview(surface) &&
+ SurfaceUtils.isSurfaceForHwVideoEncoder(surface)) {
+ throw new IllegalArgumentException("This output surface can not be both preview"
+ + " and hardware video encoding surface");
+ }
+ }
+
+ // For 2 output surface case, they shouldn't be same type.
+ if (surfaces.size() == 2) {
+ // Up to here, each surface can only be either preview or recording.
+ Iterator<Surface> iterator = surfaces.iterator();
+ boolean isFirstSurfacePreview =
+ SurfaceUtils.isSurfaceForPreview(iterator.next());
+ boolean isSecondSurfacePreview =
+ SurfaceUtils.isSurfaceForPreview(iterator.next());
+ if (isFirstSurfacePreview == isSecondSurfacePreview) {
+ throw new IllegalArgumentException("The 2 output surfaces must have different"
+ + " type");
+ }
+ }
+ }
+
@Override
public void createConstrainedHighSpeedCaptureSession(List<Surface> outputs,
android.hardware.camera2.CameraCaptureSession.StateCallback callback, Handler handler)
throws CameraAccessException {
- // TODO: to be implemented
- throw new UnsupportedOperationException("To be implemented!!!!");
+ if (outputs == null || outputs.size() == 0 || outputs.size() > 2) {
+ throw new IllegalArgumentException(
+ "Output surface list must not be null and the size must be no more than 2");
+ }
+ checkConstrainedHighSpeedSurfaces(outputs, /*fpsRange*/null);
+
+ List<OutputConfiguration> outConfigurations = new ArrayList<>(outputs.size());
+ for (Surface surface : outputs) {
+ outConfigurations.add(new OutputConfiguration(surface));
+ }
+ createCaptureSessionInternal(null, outConfigurations, callback, handler,
+ /*isConstrainedHighSpeed*/true);
}
@Override
public List<CaptureRequest> createConstrainedHighSpeedRequestList(CaptureRequest request)
throws CameraAccessException {
- throw new UnsupportedOperationException("To be implemented!!!!");
+ if (request == null) {
+ throw new IllegalArgumentException("Input capture request must not be null");
+ }
+ Collection<Surface> outputSurfaces = request.getTargets();
+ Range<Integer> fpsRange = request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE);
+ checkConstrainedHighSpeedSurfaces(outputSurfaces, fpsRange);
+
+ // Request list size: to limit the preview to 30fps, need use maxFps/30; to maximize
+ // the preview frame rate, should use maxBatch size for that high speed stream
+ // configuration. We choose the former for now.
+ int requestListSize = fpsRange.getUpper() / 30;
+ List<CaptureRequest> requestList = new ArrayList<CaptureRequest>();
+
+ // Prepare the Request builders: need carry over the request controls.
+ // First, create a request builder that will only include preview or recording target.
+ CameraMetadataNative requestMetadata = new CameraMetadataNative(request.getNativeCopy());
+ CaptureRequest.Builder singleTargetRequestBuilder = new CaptureRequest.Builder(
+ requestMetadata, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE);
+
+ // Overwrite the capture intent to make sure a good value is set.
+ Surface[] surfaces = (Surface[])outputSurfaces.toArray();
+ if (outputSurfaces.size() == 1 && SurfaceUtils.isSurfaceForHwVideoEncoder(surfaces[0])) {
+ singleTargetRequestBuilder.set(CaptureRequest.CONTROL_CAPTURE_INTENT,
+ CaptureRequest.CONTROL_CAPTURE_INTENT_PREVIEW);
+ } else {
+ // Video only, or preview + video
+ singleTargetRequestBuilder.set(CaptureRequest.CONTROL_CAPTURE_INTENT,
+ CaptureRequest.CONTROL_CAPTURE_INTENT_VIDEO_RECORD);
+ }
+ singleTargetRequestBuilder.setPartOfCHSRequestList(/*partOfCHSList*/true);
+
+ // Second, Create a request builder that will include both preview and recording targets.
+ CaptureRequest.Builder doubleTargetRequestBuilder = null;
+ if (outputSurfaces.size() == 2) {
+ doubleTargetRequestBuilder = new CaptureRequest.Builder(
+ requestMetadata, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE);
+ doubleTargetRequestBuilder.set(CaptureRequest.CONTROL_CAPTURE_INTENT,
+ CaptureRequest.CONTROL_CAPTURE_INTENT_VIDEO_RECORD);
+ doubleTargetRequestBuilder.addTarget(surfaces[0]);
+ doubleTargetRequestBuilder.addTarget(surfaces[1]);
+ doubleTargetRequestBuilder.setPartOfCHSRequestList(/*partOfCHSList*/true);
+ // Make sure singleTargetRequestBuilder contains only recording surface for
+ // preview + recording case.
+ Surface recordingSurface = surfaces[0];
+ if (!SurfaceUtils.isSurfaceForHwVideoEncoder(recordingSurface)) {
+ recordingSurface = surfaces[1];
+ }
+ singleTargetRequestBuilder.addTarget(recordingSurface);
+ } else {
+ // Single output case: either recording or preview.
+ singleTargetRequestBuilder.addTarget(surfaces[0]);
+ }
+
+ // Generate the final request list.
+ for (int i = 0; i < requestListSize; i++) {
+ if (i == 0 && doubleTargetRequestBuilder != null) {
+ // First request should be recording + preview request
+ requestList.add(doubleTargetRequestBuilder.build());
+ } else {
+ requestList.add(singleTargetRequestBuilder.build());
+ }
+ }
+
+ return Collections.unmodifiableList(requestList);
}
}
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index bc0a3a8..e963a0d 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -465,7 +465,7 @@ public class CameraDeviceUserShim implements ICameraDeviceUser {
}
@Override
- public int endConfigure() {
+ public int endConfigure(boolean isConstrainedHighSpeed) {
if (DEBUG) {
Log.d(TAG, "endConfigure called.");
}
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index 098c2d8..cc9d496 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -82,6 +82,7 @@ public class LegacyCameraDevice implements AutoCloseable {
private static final int GRALLOC_USAGE_SW_READ_OFTEN = 0x00000003;
private static final int GRALLOC_USAGE_HW_TEXTURE = 0x00000100;
private static final int GRALLOC_USAGE_HW_COMPOSER = 0x00000800;
+ private static final int GRALLOC_USAGE_HW_RENDER = 0x00000200;
private static final int GRALLOC_USAGE_HW_VIDEO_ENCODER = 0x00010000;
public static final int MAX_DIMEN_FOR_ROUNDING = 1080; // maximum allowed width for rounding
@@ -549,6 +550,42 @@ public class LegacyCameraDevice implements AutoCloseable {
return flexibleConsumer;
}
+ public static boolean isPreviewConsumer(Surface output) {
+ int usageFlags = detectSurfaceUsageFlags(output);
+ int disallowedFlags = GRALLOC_USAGE_HW_VIDEO_ENCODER | GRALLOC_USAGE_RENDERSCRIPT |
+ GRALLOC_USAGE_SW_READ_OFTEN;
+ int allowedFlags = GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_COMPOSER |
+ GRALLOC_USAGE_HW_RENDER;
+ boolean previewConsumer = ((usageFlags & disallowedFlags) == 0 &&
+ (usageFlags & allowedFlags) != 0);
+ int surfaceFormat = ImageFormat.UNKNOWN;
+ try {
+ surfaceFormat = detectSurfaceType(output);
+ } catch(BufferQueueAbandonedException e) {
+ throw new IllegalArgumentException("Surface was abandoned", e);
+ }
+
+ return previewConsumer && (surfaceFormat == ImageFormat.PRIVATE);
+ }
+
+ public static boolean isVideoEncoderConsumer(Surface output) {
+ int usageFlags = detectSurfaceUsageFlags(output);
+ int disallowedFlags = GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_COMPOSER |
+ GRALLOC_USAGE_RENDERSCRIPT | GRALLOC_USAGE_SW_READ_OFTEN;
+ int allowedFlags = GRALLOC_USAGE_HW_VIDEO_ENCODER;
+ boolean videoEncoderConsumer = ((usageFlags & disallowedFlags) == 0 &&
+ (usageFlags & allowedFlags) != 0);
+
+ int surfaceFormat = ImageFormat.UNKNOWN;
+ try {
+ surfaceFormat = detectSurfaceType(output);
+ } catch(BufferQueueAbandonedException e) {
+ throw new IllegalArgumentException("Surface was abandoned", e);
+ }
+
+ return videoEncoderConsumer && (surfaceFormat == ImageFormat.PRIVATE);
+ }
+
/**
* Query the surface for its currently configured usage flags
*/
diff --git a/core/java/android/hardware/camera2/utils/SurfaceUtils.java b/core/java/android/hardware/camera2/utils/SurfaceUtils.java
new file mode 100644
index 0000000..32e74e2
--- /dev/null
+++ b/core/java/android/hardware/camera2/utils/SurfaceUtils.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.utils;
+
+import android.hardware.camera2.legacy.LegacyCameraDevice;
+import android.hardware.camera2.legacy.LegacyExceptionUtils.BufferQueueAbandonedException;
+import android.util.Size;
+import android.view.Surface;
+
+/**
+ * Various Surface utilities.
+ */
+public class SurfaceUtils {
+
+ /**
+ * Check if a surface is for preview consumer.
+ *
+ * @param surface The surface to be checked.
+ * @return true if the surface is for preview consumer, false otherwise.
+ */
+ public static boolean isSurfaceForPreview(Surface surface) {
+ return LegacyCameraDevice.isPreviewConsumer(surface);
+ }
+
+ /**
+ * Check if the surface is for hardware video encoder consumer.
+ *
+ * @param surface The surface to be checked.
+ * @return true if the surface is for hardware video encoder consumer, false otherwise.
+ */
+ public static boolean isSurfaceForHwVideoEncoder(Surface surface) {
+ return LegacyCameraDevice.isVideoEncoderConsumer(surface);
+ }
+
+ /**
+ * Get the Surface size.
+ *
+ * @param surface The surface to be queried for size.
+ * @return Size of the surface.
+ *
+ * @throws IllegalArgumentException if the surface is already abandoned.
+ */
+ public static Size getSurfaceSize(Surface surface) {
+ try {
+ return LegacyCameraDevice.getSurfaceSize(surface);
+ } catch (BufferQueueAbandonedException e) {
+ throw new IllegalArgumentException("Surface was abandoned", e);
+ }
+ }
+
+ /**
+ * Get the Surface format.
+ *
+ * @param surface The surface to be queried for format.
+ * @return format of the surface.
+ *
+ * @throws IllegalArgumentException if the surface is already abandoned.
+ */
+ public static int getSurfaceFormat(Surface surface) {
+ try {
+ return LegacyCameraDevice.detectSurfaceType(surface);
+ } catch (BufferQueueAbandonedException e) {
+ throw new IllegalArgumentException("Surface was abandoned", e);
+ }
+ }
+}
diff --git a/core/java/android/inputmethodservice/ExtractEditText.java b/core/java/android/inputmethodservice/ExtractEditText.java
index 48b604c..f965f54 100644
--- a/core/java/android/inputmethodservice/ExtractEditText.java
+++ b/core/java/android/inputmethodservice/ExtractEditText.java
@@ -106,7 +106,7 @@ public class ExtractEditText extends EditText {
if (mIME != null && mIME.onExtractTextContextMenuItem(id)) {
// Mode was started on Extracted, needs to be stopped here.
// Cut and paste will change the text, which stops selection mode.
- if (id == android.R.id.copy) stopSelectionActionMode();
+ if (id == android.R.id.copy) stopTextActionMode();
return true;
}
return super.onTextContextMenuItem(id);
diff --git a/core/java/android/os/DropBoxManager.java b/core/java/android/os/DropBoxManager.java
index 27001dc..2b14468 100644
--- a/core/java/android/os/DropBoxManager.java
+++ b/core/java/android/os/DropBoxManager.java
@@ -225,7 +225,8 @@ public class DropBoxManager {
if ((flags & HAS_BYTE_ARRAY) != 0) {
return new Entry(tag, millis, in.createByteArray(), flags & ~HAS_BYTE_ARRAY);
} else {
- return new Entry(tag, millis, in.readFileDescriptor(), flags);
+ ParcelFileDescriptor pfd = ParcelFileDescriptor.CREATOR.createFromParcel(in);
+ return new Entry(tag, millis, pfd, flags);
}
}
};
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index c24f3fe..1c9c713 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -915,8 +915,6 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
*/
@Override
public void writeToParcel(Parcel out, int flags) {
- // WARNING: This must stay in sync with Parcel::readParcelFileDescriptor()
- // in frameworks/native/libs/binder/Parcel.cpp
if (mWrapped != null) {
try {
mWrapped.writeToParcel(out, flags);
@@ -924,12 +922,13 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
releaseResources();
}
} else {
- out.writeFileDescriptor(mFd);
if (mCommFd != null) {
out.writeInt(1);
+ out.writeFileDescriptor(mFd);
out.writeFileDescriptor(mCommFd);
} else {
out.writeInt(0);
+ out.writeFileDescriptor(mFd);
}
if ((flags & PARCELABLE_WRITE_RETURN_VALUE) != 0 && !mClosed) {
// Not a real close, so emit no status
@@ -942,11 +941,10 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
= new Parcelable.Creator<ParcelFileDescriptor>() {
@Override
public ParcelFileDescriptor createFromParcel(Parcel in) {
- // WARNING: This must stay in sync with Parcel::writeParcelFileDescriptor()
- // in frameworks/native/libs/binder/Parcel.cpp
+ int hasCommChannel = in.readInt();
final FileDescriptor fd = in.readRawFileDescriptor();
FileDescriptor commChannel = null;
- if (in.readInt() != 0) {
+ if (hasCommChannel != 0) {
commChannel = in.readRawFileDescriptor();
}
return new ParcelFileDescriptor(fd, commChannel);
diff --git a/core/java/android/provider/AlarmClock.java b/core/java/android/provider/AlarmClock.java
index 25a35e1..63ae9a9 100644
--- a/core/java/android/provider/AlarmClock.java
+++ b/core/java/android/provider/AlarmClock.java
@@ -50,7 +50,7 @@ public final class AlarmClock {
* {@link android.app.Activity#isVoiceInteraction}, and if true, the implementation should
* report a deeplink of the created/enabled alarm using
* {@link android.app.VoiceInteractor.CompleteVoiceRequest}. This allows follow-on voice actions
- * such as {@link #ACTION_VOICE_CANCEL_ALARM} to cancel the alarm that was just enabled.
+ * such as {@link #ACTION_DISMISS_ALARM} to dismiss the alarm that was just enabled.
* </p>
* <h3>Request parameters</h3>
* <ul>
@@ -69,46 +69,61 @@ public final class AlarmClock {
public static final String ACTION_SET_ALARM = "android.intent.action.SET_ALARM";
/**
- * Voice Activity Action: Cancel an alarm.
- * Requires: The activity must check {@link android.app.Activity#isVoiceInteraction}, i.e. be
- * started in Voice Interaction mode.
+ * Activity Action: Dismiss an alarm.
* <p>
- * Cancels the specified alarm by voice. To cancel means to disable, but not delete, the alarm.
- * See {@link #ACTION_VOICE_DELETE_ALARM} to delete an alarm by voice.
- * </p><p>
- * The alarm to cancel can be specified or searched for in one of the following ways:
+ * The alarm to dismiss can be specified or searched for in one of the following ways:
* <ol>
- * <li>The Intent's data URI specifies a deeplink to the alarm.
- * <li>If the Intent's data URI is unspecified, then the extra {@link #EXTRA_ALARM_SEARCH_MODE} is
- * required to determine how to search for the alarm.
+ * <li>The Intent's data URI, which represents a deeplink to the alarm.
+ * <li>The extra {@link #EXTRA_ALARM_SEARCH_MODE} to determine how to search for the alarm.
+ * </ol>
+ * </p><p>
+ * If neither of the above are given then:
+ * <ul>
+ * <li>If exactly one active alarm exists, it is dismissed.
+ * <li>If more than one active alarm exists, the user is prompted to choose the alarm to dismiss.
+ * </ul>
+ * </p><p>
+ * If the extra {@link #EXTRA_ALARM_SEARCH_MODE} is used, and the search results contain two or
+ * more matching alarms, then the implementation should show an UI with the results and allow
+ * the user to select the alarm to dismiss. If the implementation supports
+ * {@link android.content.Intent#CATEGORY_VOICE} and the activity is started in Voice
+ * Interaction mode (i.e. check {@link android.app.Activity#isVoiceInteraction}), then the
+ * implementation should additionally use {@link android.app.VoiceInteractor.PickOptionRequest}
+ * to start a voice interaction follow-on flow to help the user disambiguate the alarm by voice.
+ * </p><p>
+ * If the specified alarm is a single occurrence alarm, then dismissing it effectively disables
+ * the alarm; it will never ring again unless explicitly re-enabled.
+ * </p><p>
+ * If the specified alarm is a repeating alarm, then dismissing it only prevents the upcoming
+ * instance from ringing. The alarm remains enabled so that it will still ring on the date and
+ * time of the next instance (i.e. the instance after the upcoming one).
+ * </p>
*
- * @see #ACTION_VOICE_DELETE_ALARM
* @see #EXTRA_ALARM_SEARCH_MODE
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String ACTION_VOICE_CANCEL_ALARM =
- "android.intent.action.VOICE_CANCEL_ALARM";
+ public static final String ACTION_DISMISS_ALARM =
+ "android.intent.action.DISMISS_ALARM";
/**
- * Voice Activity Action: Delete an alarm.
- * Requires: The activity must check {@link android.app.Activity#isVoiceInteraction}, i.e. be
- * started in Voice Interaction mode.
+ * Activity Action: Snooze a currently ringing alarm.
* <p>
- * Deletes the specified alarm by voice.
- * See {@link #ACTION_VOICE_CANCEL_ALARM} to cancel (disable) an alarm by voice.
+ * Snoozes the currently ringing alarm. The extra {@link #EXTRA_ALARM_SNOOZE_DURATION} can be
+ * optionally set to specify the snooze duration; if unset, the implementation should use a
+ * reasonable default, for example 10 minutes. The alarm should ring again after the snooze
+ * duration.
* </p><p>
- * The alarm to delete can be specified or searched for in one of the following ways:
- * <ol>
- * <li>The Intent's data URI specifies a deeplink to the alarm.
- * <li>If the Intent's data URI is unspecified, then the extra {@link #EXTRA_ALARM_SEARCH_MODE} is
- * required to determine how to search for the alarm.
+ * Note: setting the extra {@link #EXTRA_ALARM_SNOOZE_DURATION} does not change the default
+ * snooze duration; it's only applied to the currently ringing alarm.
+ * </p><p>
+ * If there is no currently ringing alarm, then this is a no-op.
+ * </p>
*
- * @see #ACTION_VOICE_CANCEL_ALARM
- * @see #EXTRA_ALARM_SEARCH_MODE
+ * @see #EXTRA_ALARM_SNOOZE_DURATION
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String ACTION_VOICE_DELETE_ALARM =
- "android.intent.action.VOICE_DELETE_ALARM";
+ public static final String ACTION_SNOOZE_ALARM =
+ "android.intent.action.SNOOZE_ALARM";
/**
* Activity Action: Set a timer.
@@ -149,10 +164,9 @@ public final class AlarmClock {
/**
* Bundle extra: Specify the type of search mode to look up an alarm.
* <p>
- * Used by {@link #ACTION_VOICE_CANCEL_ALARM} and {@link #ACTION_VOICE_DELETE_ALARM} to identify
- * the alarm(s) to cancel or delete, respectively.
+ * For example, used by {@link #ACTION_DISMISS_ALARM} to identify the alarm to dismiss.
* </p><p>
- * This extra is only required when the alarm is not already identified by a deeplink as
+ * This extra is only used when the alarm is not already identified by a deeplink as
* specified in the Intent's data URI.
* </p><p>
* The value of this extra is a {@link String}, restricted to the following set of supported
@@ -164,22 +178,19 @@ public final class AlarmClock {
* <li><i>Next alarm</i> - {@link #ALARM_SEARCH_MODE_NEXT}: Selects the alarm that will
* ring next, or the alarm that is currently ringing, if any.
* <li><i>All alarms</i> - {@link #ALARM_SEARCH_MODE_ALL}: Selects all alarms.
- * <li><i>None</i> - {@link #ALARM_SEARCH_MODE_NONE}: No search mode specified. The
- * implementation should ask the user to select a search mode using
- * {@link android.app.VoiceInteractor.PickOptionRequest} and proceed with a voice flow to
- * identify the alarm.
+ * <li><i>Label</i> - {@link #ALARM_SEARCH_MODE_LABEL}: Search by alarm label. Should return
+ * alarms that contain the word or phrase in given label.
* </ul>
- * </ol>
+ * </p>
*
* @see #ALARM_SEARCH_MODE_TIME
* @see #ALARM_SEARCH_MODE_NEXT
* @see #ALARM_SEARCH_MODE_ALL
- * @see #ALARM_SEARCH_MODE_NONE
- * @see #ACTION_VOICE_CANCEL_ALARM
- * @see #ACTION_VOICE_DELETE_ALARM
+ * @see #ALARM_SEARCH_MODE_LABEL
+ * @see #ACTION_DISMISS_ALARM
*/
public static final String EXTRA_ALARM_SEARCH_MODE =
- "android.intent.extra.alarm.ALARM_SEARCH_MODE";
+ "android.intent.extra.alarm.SEARCH_MODE";
/**
* Search for the alarm that is most closely matched by the search parameters
@@ -193,35 +204,33 @@ public final class AlarmClock {
*
* @see #EXTRA_ALARM_SEARCH_MODE
*/
- public static final String ALARM_SEARCH_MODE_TIME = "time";
+ public static final String ALARM_SEARCH_MODE_TIME = "android.time";
/**
* Selects the alarm that will ring next, or the alarm that is currently ringing, if any.
*
* @see #EXTRA_ALARM_SEARCH_MODE
*/
- public static final String ALARM_SEARCH_MODE_NEXT = "next";
+ public static final String ALARM_SEARCH_MODE_NEXT = "android.next";
/**
* Selects all alarms.
*
* @see #EXTRA_ALARM_SEARCH_MODE
*/
- public static final String ALARM_SEARCH_MODE_ALL = "all";
+ public static final String ALARM_SEARCH_MODE_ALL = "android.all";
/**
- * No search mode specified. The implementation should ask the user to select a search mode
- * using {@link android.app.VoiceInteractor.PickOptionRequest} and proceed with a voice flow to
- * identify the alarm.
+ * Search by alarm label. Should return alarms that contain the word or phrase in given label.
*
* @see #EXTRA_ALARM_SEARCH_MODE
*/
- public static final String ALARM_SEARCH_MODE_NONE = "none";
+ public static final String ALARM_SEARCH_MODE_LABEL = "android.label";
/**
* Bundle extra: The AM/PM of the alarm.
* <p>
- * Used by {@link #ACTION_VOICE_CANCEL_ALARM} and {@link #ACTION_VOICE_DELETE_ALARM}.
+ * Used by {@link #ACTION_DISMISS_ALARM}.
* </p><p>
* This extra is optional and only used when {@link #EXTRA_ALARM_SEARCH_MODE} is set to
* {@link #ALARM_SEARCH_MODE_TIME}. In this search mode, the {@link #EXTRA_IS_PM} is
@@ -233,13 +242,25 @@ public final class AlarmClock {
* The value is a {@link Boolean}, where false=AM and true=PM.
* </p>
*
- * @see #ACTION_VOICE_CANCEL_ALARM
- * @see #ACTION_VOICE_DELETE_ALARM
+ * @see #ACTION_DISMISS_ALARM
* @see #EXTRA_HOUR
* @see #EXTRA_MINUTES
*/
public static final String EXTRA_IS_PM = "android.intent.extra.alarm.IS_PM";
+
+ /**
+ * Bundle extra: The snooze duration of the alarm in minutes.
+ * <p>
+ * Used by {@link #ACTION_SNOOZE_ALARM}. This extra is optional and the value is an
+ * {@link Integer} that specifies the duration in minutes for which to snooze the alarm.
+ * </p>
+ *
+ * @see #ACTION_SNOOZE_ALARM
+ */
+ public static final String EXTRA_ALARM_SNOOZE_DURATION =
+ "android.intent.extra.alarm.SNOOZE_DURATION";
+
/**
* Bundle extra: Weekdays for repeating alarm.
* <p>
diff --git a/core/java/android/security/IKeystoreService.aidl b/core/java/android/security/IKeystoreService.aidl
index 2097d5a..409542d 100644
--- a/core/java/android/security/IKeystoreService.aidl
+++ b/core/java/android/security/IKeystoreService.aidl
@@ -67,7 +67,8 @@ interface IKeystoreService {
OperationResult begin(IBinder appToken, String alias, int purpose, boolean pruneable,
in KeymasterArguments params, in byte[] entropy);
OperationResult update(IBinder token, in KeymasterArguments params, in byte[] input);
- OperationResult finish(IBinder token, in KeymasterArguments params, in byte[] signature);
+ OperationResult finish(IBinder token, in KeymasterArguments params, in byte[] signature,
+ in byte[] entropy);
int abort(IBinder handle);
boolean isOperationAuthorized(IBinder token);
int addAuthToken(in byte[] authToken);
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index d0e5b9e..a36e66c 100644
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -79,6 +79,14 @@ public class DisplayMetrics {
* This is not a density that applications should target, instead relying
* on the system to scale their {@link #DENSITY_XXHIGH} assets for them.
*/
+ public static final int DENSITY_360 = 360;
+
+ /**
+ * Intermediate density for screens that sit somewhere between
+ * {@link #DENSITY_XHIGH} (320 dpi) and {@link #DENSITY_XXHIGH} (480 dpi).
+ * This is not a density that applications should target, instead relying
+ * on the system to scale their {@link #DENSITY_XXHIGH} assets for them.
+ */
public static final int DENSITY_400 = 400;
/**
diff --git a/core/java/android/util/StateSet.java b/core/java/android/util/StateSet.java
index 83dfc47..c2a6a7a 100644
--- a/core/java/android/util/StateSet.java
+++ b/core/java/android/util/StateSet.java
@@ -119,7 +119,14 @@ public class StateSet {
/** @hide */
public StateSet() {}
+ /**
+ * A state specification that will be matched by all StateSets.
+ */
public static final int[] WILD_CARD = new int[0];
+
+ /**
+ * A state set that does not contain any valid states.
+ */
public static final int[] NOTHING = new int[] { 0 };
/**
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
index 7b0f1fb..37e4000 100644
--- a/core/java/android/view/ScaleGestureDetector.java
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -516,8 +516,8 @@ public class ScaleGestureDetector {
}
/**
- * Return whether the stylus scale gesture, in which the user uses a stylus
- * and presses the button, should preform scaling. {@see #setButtonScaleEnabled(boolean)}.
+ * Return whether the stylus scale gesture, in which the user uses a stylus and presses the
+ * button, should perform scaling. {@see #setStylusScaleEnabled(boolean)}
*/
public boolean isStylusScaleEnabled() {
return mStylusScaleEnabled;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 126540f..9269fd2 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -17148,6 +17148,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* drawable.
*
* @return The color of the ColorDrawable background, if set, otherwise 0.
+ * @hide
*/
@ColorInt
public int getBackgroundColor() {
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 9e0719d..997e7e8 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -251,7 +251,7 @@ public final class WindowInsets {
* @return true if any inset values are nonzero
*/
public boolean hasInsets() {
- return hasSystemWindowInsets() || hasWindowDecorInsets();
+ return hasSystemWindowInsets() || hasWindowDecorInsets() || hasStableInsets();
}
/**
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 053b35c..1c8a79b 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1169,7 +1169,10 @@ public final class InputMethodManager {
// do its stuff.
// Life is good: let's hook everything up!
EditorInfo tba = new EditorInfo();
- tba.packageName = view.getContext().getPackageName();
+ // Note: Use Context#getOpPackageName() rather than Context#getPackageName() so that the
+ // system can verify the consistency between the uid of this process and package name passed
+ // from here. See comment of Context#getOpPackageName() for details.
+ tba.packageName = view.getContext().getOpPackageName();
tba.fieldId = view.getId();
InputConnection ic = view.onCreateInputConnection(tba);
if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic);
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 7f85f5a..0001860 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -694,9 +694,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
*/
private boolean mForceTranscriptScroll;
- private int mGlowPaddingLeft;
- private int mGlowPaddingRight;
-
/**
* Used for interacting with list items from an accessibility service.
*/
@@ -3489,17 +3486,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (!mEdgeGlowBottom.isFinished()) {
mEdgeGlowBottom.onRelease();
}
- invalidate(0, 0, getWidth(),
- mEdgeGlowTop.getMaxHeight() + getPaddingTop());
+ invalidateTopGlow();
} else if (incrementalDeltaY < 0) {
mEdgeGlowBottom.onPull((float) overscroll / getHeight(),
1.f - (float) x / getWidth());
if (!mEdgeGlowTop.isFinished()) {
mEdgeGlowTop.onRelease();
}
- invalidate(0, getHeight() - getPaddingBottom() -
- mEdgeGlowBottom.getMaxHeight(), getWidth(),
- getHeight());
+ invalidateBottomGlow();
}
}
}
@@ -3539,17 +3533,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (!mEdgeGlowBottom.isFinished()) {
mEdgeGlowBottom.onRelease();
}
- invalidate(0, 0, getWidth(),
- mEdgeGlowTop.getMaxHeight() + getPaddingTop());
+ invalidateTopGlow();
} else if (rawDeltaY < 0) {
mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight(),
1.f - (float) x / getWidth());
if (!mEdgeGlowTop.isFinished()) {
mEdgeGlowTop.onRelease();
}
- invalidate(0, getHeight() - getPaddingBottom() -
- mEdgeGlowBottom.getMaxHeight(), getWidth(),
- getHeight());
+ invalidateBottomGlow();
}
}
}
@@ -3581,6 +3572,28 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
+ private void invalidateTopGlow() {
+ if (mEdgeGlowTop == null) {
+ return;
+ }
+ final boolean clipToPadding = getClipToPadding();
+ final int top = clipToPadding ? mPaddingTop : 0;
+ final int left = clipToPadding ? mPaddingLeft : 0;
+ final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth();
+ invalidate(left, top, right, top + mEdgeGlowTop.getMaxHeight());
+ }
+
+ private void invalidateBottomGlow() {
+ if (mEdgeGlowBottom == null) {
+ return;
+ }
+ final boolean clipToPadding = getClipToPadding();
+ final int bottom = clipToPadding ? getHeight() - mPaddingBottom : getHeight();
+ final int left = clipToPadding ? mPaddingLeft : 0;
+ final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth();
+ invalidate(left, bottom - mEdgeGlowBottom.getMaxHeight(), right, bottom);
+ }
+
@Override
public void onTouchModeChanged(boolean isInTouchMode) {
if (isInTouchMode) {
@@ -4142,47 +4155,53 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
super.draw(canvas);
if (mEdgeGlowTop != null) {
final int scrollY = mScrollY;
+ final boolean clipToPadding = getClipToPadding();
+ final int width;
+ final int height;
+ final int translateX;
+ final int translateY;
+
+ if (clipToPadding) {
+ width = getWidth() - mPaddingLeft - mPaddingRight;
+ height = getHeight() - mPaddingTop - mPaddingBottom;
+ translateX = mPaddingLeft;
+ translateY = mPaddingTop;
+ } else {
+ width = getWidth();
+ height = getHeight();
+ translateX = 0;
+ translateY = 0;
+ }
if (!mEdgeGlowTop.isFinished()) {
final int restoreCount = canvas.save();
- final int width = getWidth();
-
- int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess);
- canvas.translate(0, edgeY);
- mEdgeGlowTop.setSize(width, getHeight());
+ canvas.clipRect(translateX, translateY,
+ translateX + width ,translateY + mEdgeGlowTop.getMaxHeight());
+ final int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess) + translateY;
+ canvas.translate(translateX, edgeY);
+ mEdgeGlowTop.setSize(width, height);
if (mEdgeGlowTop.draw(canvas)) {
- invalidate(0, 0, getWidth(),
- mEdgeGlowTop.getMaxHeight() + getPaddingTop());
+ invalidateTopGlow();
}
canvas.restoreToCount(restoreCount);
}
if (!mEdgeGlowBottom.isFinished()) {
final int restoreCount = canvas.save();
- final int width = getWidth();
- final int height = getHeight();
-
- int edgeX = -width;
- int edgeY = Math.max(height, scrollY + mLastPositionDistanceGuess);
+ canvas.clipRect(translateX, translateY + height - mEdgeGlowBottom.getMaxHeight(),
+ translateX + width, translateY + height);
+ final int edgeX = -width + translateX;
+ final int edgeY = Math.max(getHeight(), scrollY + mLastPositionDistanceGuess)
+ - (clipToPadding ? mPaddingBottom : 0);
canvas.translate(edgeX, edgeY);
canvas.rotate(180, width, 0);
mEdgeGlowBottom.setSize(width, height);
if (mEdgeGlowBottom.draw(canvas)) {
- invalidate(0, getHeight() - getPaddingBottom() -
- mEdgeGlowBottom.getMaxHeight(), getWidth(),
- getHeight());
+ invalidateBottomGlow();
}
canvas.restoreToCount(restoreCount);
}
}
}
- /**
- * @hide
- */
- public void setOverScrollEffectPadding(int leftPadding, int rightPadding) {
- mGlowPaddingLeft = leftPadding;
- mGlowPaddingRight = rightPadding;
- }
-
private void initOrResetVelocityTracker() {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
@@ -5309,13 +5328,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
@Override
- void syncSelectedItem() {
- if (mDataChanged) {
- layoutChildren();
- }
- }
-
- @Override
protected void handleDataChanged() {
int count = mItemCount;
int lastHandledItemCount = mLastHandledItemCount;
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index cfe02bd..6962711 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -551,67 +551,37 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
}
/**
- * Returns the position of the currently selected item within the adapter's
- * data set, or {@link #INVALID_POSITION} if there is nothing selected.
- * <p>
- * <strong>Note:</strong> Prior to {@link android.os.Build.VERSION_CODES#MNC},
- * calling this method between an adapter data set change and a subsequent
- * layout pass could return invalid data.
+ * Return the position of the currently selected item within the adapter's data set
*
- * @return the selected item's position (starting at 0), or
- * {@link #INVALID_POSITION} if there is nothing selected
+ * @return int Position (starting at 0), or {@link #INVALID_POSITION} if there is nothing selected.
*/
@ViewDebug.CapturedViewProperty
public int getSelectedItemPosition() {
- syncSelectedItem();
return mNextSelectedPosition;
}
/**
- * Returns the row ID corresponding to the currently selected item, or
- * {@link #INVALID_ROW_ID} if nothing is selected.
- * <p>
- * <strong>Note:</strong> Prior to {@link android.os.Build.VERSION_CODES#MNC},
- * calling this method between an adapter data set change and a subsequent
- * layout pass could return invalid data.
- *
- * @return the selected item's row ID, or {@link #INVALID_ROW_ID} if
- * nothing is selected
+ * @return The id corresponding to the currently selected item, or {@link #INVALID_ROW_ID}
+ * if nothing is selected.
*/
@ViewDebug.CapturedViewProperty
public long getSelectedItemId() {
- syncSelectedItem();
return mNextSelectedRowId;
}
/**
- * Returns the view corresponding to the currently selected item, or
- * {@code null} if nothing is selected.
- * <p>
- * <strong>Note:</strong> Prior to {@link android.os.Build.VERSION_CODES#MNC},
- * calling this method between an adapter data set change and a subsequent
- * layout pass could return inconsistent data.
- *
- * @return the selected item's view, or {@code null} if nothing is selected
+ * @return The view corresponding to the currently selected item, or null
+ * if nothing is selected
*/
- @Nullable
public abstract View getSelectedView();
/**
- * Returns the data corresponding to the currently selected item, or
- * {@code null} if nothing is selected.
- * <p>
- * <strong>Note:</strong> Prior to {@link android.os.Build.VERSION_CODES#MNC},
- * calling this method between an adapter data set change and a subsequent
- * layout pass could return inconsistent data.
- *
- * @return the data corresponding to the currently selected item, or
- * {@code null} if there is nothing selected.
+ * @return The data corresponding to the currently selected item, or
+ * null if there is nothing selected.
*/
- @Nullable
public Object getSelectedItem() {
- final T adapter = getAdapter();
- final int selection = getSelectedItemPosition();
+ T adapter = getAdapter();
+ int selection = getSelectedItemPosition();
if (adapter != null && adapter.getCount() > 0 && selection >= 0) {
return adapter.getItem(selection);
} else {
@@ -620,15 +590,6 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
}
/**
- * Synchronizes the selected item's position and ID, if necessary.
- */
- void syncSelectedItem() {
- if (mDataChanged) {
- onLayout(false, mLeft, mTop, mRight, mBottom);
- }
- }
-
- /**
* @return The number of items owned by the Adapter associated with this
* AdapterView. (This is the number of data items, which may be
* larger than the number of visible views.)
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 56f9b5c..c8d8241 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -125,6 +125,15 @@ public class Editor {
// Tag used when the Editor maintains its own separate UndoManager.
private static final String UNDO_OWNER_TAG = "Editor";
+ // Ordering constants used to place the Action Mode items in their menu.
+ private static final int MENU_ITEM_ORDER_CUT = 1;
+ private static final int MENU_ITEM_ORDER_COPY = 2;
+ private static final int MENU_ITEM_ORDER_PASTE = 3;
+ private static final int MENU_ITEM_ORDER_SHARE = 4;
+ private static final int MENU_ITEM_ORDER_SELECT_ALL = 5;
+ private static final int MENU_ITEM_ORDER_REPLACE = 6;
+ private static final int MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 10;
+
// Each Editor manages its own undo stack.
private final UndoManager mUndoManager = new UndoManager();
private UndoOwner mUndoOwner = mUndoManager.getOwner(UNDO_OWNER_TAG, this);
@@ -134,7 +143,8 @@ public class Editor {
// Cursor Controllers.
InsertionPointCursorController mInsertionPointCursorController;
SelectionModifierCursorController mSelectionModifierCursorController;
- ActionMode mSelectionActionMode;
+ // Action mode used when text is selected or when actions on an insertion cursor are triggered.
+ ActionMode mTextActionMode;
boolean mInsertionControllerEnabled;
boolean mSelectionControllerEnabled;
@@ -205,13 +215,14 @@ public class Editor {
float mLastDownPositionX, mLastDownPositionY;
Callback mCustomSelectionActionModeCallback;
+ Callback mCustomInsertionActionModeCallback;
// Set when this TextView gained focus with some text selected. Will start selection mode.
boolean mCreatedWithASelection;
boolean mDoubleTap = false;
- private Runnable mSelectionModeWithoutSelectionRunnable;
+ private Runnable mInsertionActionModeRunnable;
// The span controller helps monitoring the changes to which the Editor needs to react:
// - EasyEditSpans, for which we have some UI to display on attach and on hide
@@ -236,8 +247,8 @@ public class Editor {
private final Runnable mHideFloatingToolbar = new Runnable() {
@Override
public void run() {
- if (mSelectionActionMode != null) {
- mSelectionActionMode.snooze(ActionMode.SNOOZE_TIME_DEFAULT);
+ if (mTextActionMode != null) {
+ mTextActionMode.snooze(ActionMode.SNOOZE_TIME_DEFAULT);
}
}
};
@@ -245,8 +256,8 @@ public class Editor {
private final Runnable mShowFloatingToolbar = new Runnable() {
@Override
public void run() {
- if (mSelectionActionMode != null) {
- mSelectionActionMode.snooze(0); // snooze off.
+ if (mTextActionMode != null) {
+ mTextActionMode.snooze(0); // snooze off.
}
}
};
@@ -310,7 +321,7 @@ public class Editor {
void replace() {
int middle = (mTextView.getSelectionStart() + mTextView.getSelectionEnd()) / 2;
- stopSelectionActionMode();
+ stopTextActionMode();
Selection.setSelection((Spannable) mTextView.getText(), middle);
showSuggestions();
}
@@ -343,7 +354,7 @@ public class Editor {
mTextView.setHasTransientState(false);
// We had an active selection from before, start the selection mode.
- startSelectionActionModeWithSelection();
+ startSelectionActionMode();
}
getPositionListener().addSubscriber(mCursorAnchorInfoNotifier, true);
@@ -372,8 +383,8 @@ public class Editor {
}
// Cancel the single tap delayed runnable.
- if (mSelectionModeWithoutSelectionRunnable != null) {
- mTextView.removeCallbacks(mSelectionModeWithoutSelectionRunnable);
+ if (mInsertionActionModeRunnable != null) {
+ mTextView.removeCallbacks(mInsertionActionModeRunnable);
}
mTextView.removeCallbacks(mHideFloatingToolbar);
@@ -390,7 +401,7 @@ public class Editor {
mPreserveDetachedSelection = true;
hideControllers();
- stopSelectionActionMode();
+ stopTextActionMode();
mPreserveDetachedSelection = false;
mTemporaryDetach = false;
}
@@ -586,7 +597,7 @@ public class Editor {
}
if (!mSelectionControllerEnabled) {
- stopSelectionActionMode();
+ stopTextActionMode();
if (mSelectionModifierCursorController != null) {
mSelectionModifierCursorController.onDetached();
mSelectionModifierCursorController = null;
@@ -984,14 +995,14 @@ public class Editor {
mInsertionControllerEnabled) {
final int offset = mTextView.getOffsetForPosition(mLastDownPositionX,
mLastDownPositionY);
- stopSelectionActionMode();
+ stopTextActionMode();
Selection.setSelection((Spannable) mTextView.getText(), offset);
getInsertionController().show();
- startSelectionActionModeWithoutSelection();
+ startInsertionActionMode();
handled = true;
}
- if (!handled && mSelectionActionMode != null) {
+ if (!handled && mTextActionMode != null) {
if (touchPositionIsInSelection()) {
// Start a drag
final int start = mTextView.getSelectionStart();
@@ -1001,9 +1012,9 @@ public class Editor {
DragLocalState localState = new DragLocalState(mTextView, start, end);
mTextView.startDrag(data, getTextThumbnailBuilder(selectedText), localState,
View.DRAG_FLAG_GLOBAL);
- stopSelectionActionMode();
+ stopTextActionMode();
} else {
- stopSelectionActionMode();
+ stopTextActionMode();
selectCurrentWordAndStartDrag();
}
handled = true;
@@ -1101,12 +1112,12 @@ public class Editor {
final int selStart = mTextView.getSelectionStart();
final int selEnd = mTextView.getSelectionEnd();
hideControllers();
- stopSelectionActionMode();
+ stopTextActionMode();
Selection.setSelection((Spannable) mTextView.getText(), selStart, selEnd);
} else {
if (mTemporaryDetach) mPreserveDetachedSelection = true;
hideControllers();
- stopSelectionActionMode();
+ stopTextActionMode();
if (mTemporaryDetach) mPreserveDetachedSelection = false;
downgradeEasyCorrectionSpans();
}
@@ -1149,7 +1160,7 @@ public class Editor {
// We do not hide the span controllers, since they can be added when a new text is
// inserted into the text view (voice IME).
hideCursorControllers();
- stopSelectionActionMode();
+ stopTextActionMode();
}
private int getLastTapPosition() {
@@ -1216,7 +1227,7 @@ public class Editor {
}
private void updateFloatingToolbarVisibility(MotionEvent event) {
- if (mSelectionActionMode != null) {
+ if (mTextActionMode != null) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_MOVE:
hideFloatingToolbar();
@@ -1229,7 +1240,7 @@ public class Editor {
}
private void hideFloatingToolbar() {
- if (mSelectionActionMode != null) {
+ if (mTextActionMode != null) {
mTextView.removeCallbacks(mShowFloatingToolbar);
// Delay the "hide" a little bit just in case a "show" will happen almost immediately.
mTextView.postDelayed(mHideFloatingToolbar, 100);
@@ -1237,7 +1248,7 @@ public class Editor {
}
private void showFloatingToolbar() {
- if (mSelectionActionMode != null) {
+ if (mTextActionMode != null) {
mTextView.removeCallbacks(mHideFloatingToolbar);
// Delay "show" so it doesn't interfere with click confirmations
// or double-clicks that could "dismiss" the floating toolbar.
@@ -1701,26 +1712,20 @@ public class Editor {
/**
* @return true if the selection mode was actually started.
*/
- private boolean startSelectionActionModeWithoutSelection() {
+ private boolean startInsertionActionMode() {
+ if (mInsertionActionModeRunnable != null) {
+ mTextView.removeCallbacks(mInsertionActionModeRunnable);
+ }
if (extractedTextModeWillBeStarted()) {
- // Cancel the single tap delayed runnable.
- if (mSelectionModeWithoutSelectionRunnable != null) {
- mTextView.removeCallbacks(mSelectionModeWithoutSelectionRunnable);
- }
-
return false;
}
+ stopTextActionMode();
- if (mSelectionActionMode != null) {
- // Selection action mode is already started
- // TODO: revisit invocations to minimize this case.
- mSelectionActionMode.invalidate();
- return false;
- }
- ActionMode.Callback actionModeCallback = new SelectionActionModeCallback();
- mSelectionActionMode = mTextView.startActionMode(
+ ActionMode.Callback actionModeCallback =
+ new TextActionModeCallback(false /* hasSelection */);
+ mTextActionMode = mTextView.startActionMode(
actionModeCallback, ActionMode.TYPE_FLOATING);
- return mSelectionActionMode != null;
+ return mTextActionMode != null;
}
/**
@@ -1730,8 +1735,8 @@ public class Editor {
*
* @return true if the selection mode was actually started.
*/
- boolean startSelectionActionModeWithSelection() {
- boolean selectionStarted = startSelectionActionModeWithSelectionInternal();
+ boolean startSelectionActionMode() {
+ boolean selectionStarted = startSelectionActionModeInternal();
if (selectionStarted) {
getSelectionController().show();
} else if (getInsertionController() != null) {
@@ -1749,17 +1754,23 @@ public class Editor {
private boolean selectCurrentWordAndStartDrag() {
if (extractedTextModeWillBeStarted()) {
// Cancel the single tap delayed runnable.
- if (mSelectionModeWithoutSelectionRunnable != null) {
- mTextView.removeCallbacks(mSelectionModeWithoutSelectionRunnable);
+ if (mInsertionActionModeRunnable != null) {
+ mTextView.removeCallbacks(mInsertionActionModeRunnable);
}
return false;
}
- if (mSelectionActionMode != null) {
- mSelectionActionMode.finish();
+ if (mTextActionMode != null) {
+ mTextActionMode.finish();
}
if (!checkFieldAndSelectCurrentWord()) {
return false;
}
+
+ // Avoid dismissing the selection if it exists.
+ mPreserveDetachedSelection = true;
+ stopTextActionMode();
+ mPreserveDetachedSelection = false;
+
getSelectionController().enterDrag();
return true;
}
@@ -1784,10 +1795,10 @@ public class Editor {
return true;
}
- private boolean startSelectionActionModeWithSelectionInternal() {
- if (mSelectionActionMode != null) {
+ private boolean startSelectionActionModeInternal() {
+ if (mTextActionMode != null) {
// Selection action mode is already started
- mSelectionActionMode.invalidate();
+ mTextActionMode.invalidate();
return false;
}
@@ -1800,12 +1811,13 @@ public class Editor {
// Do not start the action mode when extracted text will show up full screen, which would
// immediately hide the newly created action bar and would be visually distracting.
if (!willExtract) {
- ActionMode.Callback actionModeCallback = new SelectionActionModeCallback();
- mSelectionActionMode = mTextView.startActionMode(
+ ActionMode.Callback actionModeCallback =
+ new TextActionModeCallback(true /* hasSelection */);
+ mTextActionMode = mTextView.startActionMode(
actionModeCallback, ActionMode.TYPE_FLOATING);
}
- final boolean selectionStarted = mSelectionActionMode != null || willExtract;
+ final boolean selectionStarted = mTextActionMode != null || willExtract;
if (selectionStarted && !mTextView.isTextSelectable() && mShowSoftInputOnFocus) {
// Show the IME to be able to replace text, except when selecting non editable text.
final InputMethodManager imm = InputMethodManager.peekInstance();
@@ -1906,7 +1918,7 @@ public class Editor {
void onTouchUpEvent(MotionEvent event) {
boolean selectAllGotFocus = mSelectAllOnFocus && mTextView.didTouchFocusSelect();
hideControllers();
- stopSelectionActionMode();
+ stopTextActionMode();
CharSequence text = mTextView.getText();
if (!selectAllGotFocus && text.length() > 0) {
// Move cursor
@@ -1920,8 +1932,8 @@ public class Editor {
if (!extractedTextModeWillBeStarted()) {
if (isCursorInsideEasyCorrectionSpan()) {
// Cancel the single tap delayed runnable.
- if (mSelectionModeWithoutSelectionRunnable != null) {
- mTextView.removeCallbacks(mSelectionModeWithoutSelectionRunnable);
+ if (mInsertionActionModeRunnable != null) {
+ mTextView.removeCallbacks(mInsertionActionModeRunnable);
}
mShowSuggestionRunnable = new Runnable() {
@@ -1939,10 +1951,10 @@ public class Editor {
}
}
- protected void stopSelectionActionMode() {
- if (mSelectionActionMode != null) {
+ protected void stopTextActionMode() {
+ if (mTextActionMode != null) {
// This will hide the mSelectionModifierCursorController
- mSelectionActionMode.finish();
+ mTextActionMode.finish();
}
}
@@ -2027,7 +2039,7 @@ public class Editor {
mSuggestionsPopupWindow = new SuggestionsPopupWindow();
}
hideControllers();
- stopSelectionActionMode();
+ stopTextActionMode();
mSuggestionsPopupWindow.show();
}
@@ -2035,8 +2047,8 @@ public class Editor {
if (mPositionListener != null) {
mPositionListener.onScrollChanged();
}
- if (mSelectionActionMode != null) {
- mSelectionActionMode.invalidateContentRect();
+ if (mTextActionMode != null) {
+ mTextActionMode.invalidateContentRect();
}
}
@@ -3087,46 +3099,54 @@ public class Editor {
}
/**
- * An ActionMode Callback class that is used to provide actions while in text selection mode.
+ * An ActionMode Callback class that is used to provide actions while in text insertion or
+ * selection mode.
*
- * The default callback provides a subset of Select All, Cut, Copy and Paste actions, depending
- * on which of these this TextView supports.
+ * The default callback provides a subset of Select All, Cut, Copy, Paste, Share and Replace
+ * actions, depending on which of these this TextView supports and the current selection.
*/
- private class SelectionActionModeCallback extends ActionMode.Callback2 {
+ private class TextActionModeCallback extends ActionMode.Callback2 {
private final Path mSelectionPath = new Path();
private final RectF mSelectionBounds = new RectF();
-
- private int mSelectionHandleHeight;
- private int mInsertionHandleHeight;
-
- public SelectionActionModeCallback() {
- SelectionModifierCursorController selectionController = getSelectionController();
- if (selectionController.mStartHandle == null) {
- // As these are for initializing selectionController, hide() must be called.
- selectionController.initDrawables();
- selectionController.initHandles();
- selectionController.hide();
- }
- mSelectionHandleHeight = Math.max(
- mSelectHandleLeft.getMinimumHeight(), mSelectHandleRight.getMinimumHeight());
- InsertionPointCursorController insertionController = getInsertionController();
- if (insertionController != null) {
- insertionController.getHandle();
- mInsertionHandleHeight = mSelectHandleCenter.getMinimumHeight();
+ private final boolean mHasSelection;
+
+ private int mHandleHeight;
+
+ public TextActionModeCallback(boolean hasSelection) {
+ mHasSelection = hasSelection;
+ if (mHasSelection) {
+ SelectionModifierCursorController selectionController = getSelectionController();
+ if (selectionController.mStartHandle == null) {
+ // As these are for initializing selectionController, hide() must be called.
+ selectionController.initDrawables();
+ selectionController.initHandles();
+ selectionController.hide();
+ }
+ mHandleHeight = Math.max(
+ mSelectHandleLeft.getMinimumHeight(),
+ mSelectHandleRight.getMinimumHeight());
+ } else {
+ InsertionPointCursorController insertionController = getInsertionController();
+ if (insertionController != null) {
+ insertionController.getHandle();
+ mHandleHeight = mSelectHandleCenter.getMinimumHeight();
+ }
}
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- mode.setTitle(mTextView.getContext().getString(
- com.android.internal.R.string.textSelectionCABTitle));
+ mode.setTitle(null);
mode.setSubtitle(null);
mode.setTitleOptionalHint(true);
populateMenuWithItems(menu);
- if (mCustomSelectionActionModeCallback != null) {
- if (!mCustomSelectionActionModeCallback.onCreateActionMode(mode, menu)) {
- // The custom mode can choose to cancel the action mode
+ Callback customCallback = getCustomCallback();
+ if (customCallback != null) {
+ if (!customCallback.onCreateActionMode(mode, menu)) {
+ // The custom mode can choose to cancel the action mode, dismiss selection.
+ Selection.setSelection((Spannable) mTextView.getText(),
+ mTextView.getSelectionEnd());
return false;
}
}
@@ -3141,36 +3161,41 @@ public class Editor {
}
}
+ private Callback getCustomCallback() {
+ return mHasSelection
+ ? mCustomSelectionActionModeCallback
+ : mCustomInsertionActionModeCallback;
+ }
+
private void populateMenuWithItems(Menu menu) {
if (mTextView.canCut()) {
- menu.add(0, TextView.ID_CUT, 0, com.android.internal.R.string.cut).
+ menu.add(Menu.NONE, TextView.ID_CUT, MENU_ITEM_ORDER_CUT,
+ com.android.internal.R.string.cut).
setAlphabeticShortcut('x').
setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
}
if (mTextView.canCopy()) {
- menu.add(0, TextView.ID_COPY, 0, com.android.internal.R.string.copy).
+ menu.add(Menu.NONE, TextView.ID_COPY, MENU_ITEM_ORDER_COPY,
+ com.android.internal.R.string.copy).
setAlphabeticShortcut('c').
setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
}
if (mTextView.canPaste()) {
- menu.add(0, TextView.ID_PASTE, 0, com.android.internal.R.string.paste).
- setAlphabeticShortcut('v').
- setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
+ menu.add(Menu.NONE, TextView.ID_PASTE, MENU_ITEM_ORDER_PASTE,
+ com.android.internal.R.string.paste).
+ setAlphabeticShortcut('v').
+ setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
}
if (mTextView.canShare()) {
- menu.add(0, TextView.ID_SHARE, 0, com.android.internal.R.string.share).
- setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
- }
-
- if (mTextView.canSelectAllText()) {
- menu.add(0, TextView.ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
- setAlphabeticShortcut('a').
- setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ menu.add(Menu.NONE, TextView.ID_SHARE, MENU_ITEM_ORDER_SHARE,
+ com.android.internal.R.string.share).
+ setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
}
+ updateSelectAllItem(menu);
updateReplaceItem(menu);
}
@@ -3179,8 +3204,11 @@ public class Editor {
PackageManager packageManager = mTextView.getContext().getPackageManager();
List<ResolveInfo> supportedActivities =
packageManager.queryIntentActivities(createProcessTextIntent(), 0);
- for (ResolveInfo info : supportedActivities) {
- menu.add(info.loadLabel(packageManager))
+ for (int i = 0; i < supportedActivities.size(); ++i) {
+ ResolveInfo info = supportedActivities.get(i);
+ menu.add(Menu.NONE, Menu.NONE,
+ MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START + i,
+ info.loadLabel(packageManager))
.setIntent(createProcessTextIntentForResolveInfo(info))
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
}
@@ -3201,20 +3229,35 @@ public class Editor {
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ updateSelectAllItem(menu);
updateReplaceItem(menu);
- if (mCustomSelectionActionModeCallback != null) {
- return mCustomSelectionActionModeCallback.onPrepareActionMode(mode, menu);
+ Callback customCallback = getCustomCallback();
+ if (customCallback != null) {
+ return customCallback.onPrepareActionMode(mode, menu);
}
return true;
}
+ private void updateSelectAllItem(Menu menu) {
+ boolean canSelectAll = mTextView.canSelectAllText();
+ boolean selectAllItemExists = menu.findItem(TextView.ID_SELECT_ALL) != null;
+ if (canSelectAll && !selectAllItemExists) {
+ menu.add(Menu.NONE, TextView.ID_SELECT_ALL, MENU_ITEM_ORDER_SELECT_ALL,
+ com.android.internal.R.string.selectAll)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ } else if (!canSelectAll && selectAllItemExists) {
+ menu.removeItem(TextView.ID_SELECT_ALL);
+ }
+ }
+
private void updateReplaceItem(Menu menu) {
boolean canReplace = mTextView.isSuggestionsEnabled() && shouldOfferToShowSuggestions();
boolean replaceItemExists = menu.findItem(TextView.ID_REPLACE) != null;
if (canReplace && !replaceItemExists) {
- menu.add(0, TextView.ID_REPLACE, 0, com.android.internal.R.string.replace).
- setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ menu.add(Menu.NONE, TextView.ID_REPLACE, MENU_ITEM_ORDER_REPLACE,
+ com.android.internal.R.string.replace)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
} else if (!canReplace && replaceItemExists) {
menu.removeItem(TextView.ID_REPLACE);
}
@@ -3230,8 +3273,8 @@ public class Editor {
item.getIntent(), TextView.PROCESS_TEXT_REQUEST_CODE);
return true;
}
- if (mCustomSelectionActionModeCallback != null &&
- mCustomSelectionActionModeCallback.onActionItemClicked(mode, item)) {
+ Callback customCallback = getCustomCallback();
+ if (customCallback != null && customCallback.onActionItemClicked(mode, item)) {
return true;
}
return mTextView.onTextContextMenuItem(item.getItemId());
@@ -3239,8 +3282,9 @@ public class Editor {
@Override
public void onDestroyActionMode(ActionMode mode) {
- if (mCustomSelectionActionModeCallback != null) {
- mCustomSelectionActionModeCallback.onDestroyActionMode(mode);
+ Callback customCallback = getCustomCallback();
+ if (customCallback != null) {
+ customCallback.onDestroyActionMode(mode);
}
/*
@@ -3259,7 +3303,7 @@ public class Editor {
mSelectionModifierCursorController.resetTouchOffsets();
}
- mSelectionActionMode = null;
+ mTextActionMode = null;
}
@Override
@@ -3274,7 +3318,7 @@ public class Editor {
mTextView.getLayout().getSelectionPath(
mTextView.getSelectionStart(), mTextView.getSelectionEnd(), mSelectionPath);
mSelectionPath.computeBounds(mSelectionBounds, true);
- mSelectionBounds.bottom += mSelectionHandleHeight;
+ mSelectionBounds.bottom += mHandleHeight;
} else if (mCursorCount == 2) {
// We have a split cursor. In this case, we take the rectangle that includes both
// parts of the cursor to ensure we don't obscure either of them.
@@ -3285,7 +3329,7 @@ public class Editor {
Math.min(firstCursorBounds.top, secondCursorBounds.top),
Math.max(firstCursorBounds.right, secondCursorBounds.right),
Math.max(firstCursorBounds.bottom, secondCursorBounds.bottom)
- + mInsertionHandleHeight);
+ + mHandleHeight);
} else {
// We have a single cursor.
int line = mTextView.getLayout().getLineForOffset(mTextView.getSelectionStart());
@@ -3295,7 +3339,7 @@ public class Editor {
primaryHorizontal,
mTextView.getLayout().getLineTop(line),
primaryHorizontal + 1,
- mTextView.getLayout().getLineTop(line + 1) + mInsertionHandleHeight);
+ mTextView.getLayout().getLineTop(line + 1) + mHandleHeight);
}
// Take TextView's padding and scroll into account.
int textHorizontalOffset = mTextView.viewportToContentHorizontalOffset();
@@ -3847,25 +3891,25 @@ public class Editor {
SystemClock.uptimeMillis() - TextView.sLastCutCopyOrTextChangedTime;
// Cancel the single tap delayed runnable.
- if (mSelectionModeWithoutSelectionRunnable != null
+ if (mInsertionActionModeRunnable != null
&& (mDoubleTap || isCursorInsideEasyCorrectionSpan())) {
- mTextView.removeCallbacks(mSelectionModeWithoutSelectionRunnable);
+ mTextView.removeCallbacks(mInsertionActionModeRunnable);
}
// Prepare and schedule the single tap runnable to run exactly after the double tap
// timeout has passed.
if (!mDoubleTap && !isCursorInsideEasyCorrectionSpan()
&& (durationSinceCutOrCopy < RECENT_CUT_COPY_DURATION)) {
- if (mSelectionModeWithoutSelectionRunnable == null) {
- mSelectionModeWithoutSelectionRunnable = new Runnable() {
+ if (mInsertionActionModeRunnable == null) {
+ mInsertionActionModeRunnable = new Runnable() {
public void run() {
- startSelectionActionModeWithoutSelection();
+ startInsertionActionMode();
}
};
}
mTextView.postDelayed(
- mSelectionModeWithoutSelectionRunnable,
+ mInsertionActionModeRunnable,
ViewConfiguration.getDoubleTapTimeout() + 1);
}
@@ -3934,15 +3978,15 @@ public class Editor {
if (distanceSquared < touchSlop * touchSlop) {
// Tapping on the handle toggles the selection action mode.
- if (mSelectionActionMode != null) {
- mSelectionActionMode.finish();
+ if (mTextActionMode != null) {
+ mTextActionMode.finish();
} else {
- startSelectionActionModeWithoutSelection();
+ startInsertionActionMode();
}
}
} else {
- if (mSelectionActionMode != null) {
- mSelectionActionMode.invalidateContentRect();
+ if (mTextActionMode != null) {
+ mTextActionMode.invalidateContentRect();
}
}
hideAfterDelay();
@@ -3972,8 +4016,8 @@ public class Editor {
@Override
public void updatePosition(float x, float y) {
positionAtCursorOffset(mTextView.getOffsetForPosition(x, y), false);
- if (mSelectionActionMode != null) {
- mSelectionActionMode.invalidate();
+ if (mTextActionMode != null) {
+ mTextActionMode.invalidate();
}
}
@@ -4024,8 +4068,8 @@ public class Editor {
Selection.setSelection((Spannable) mTextView.getText(), offset,
mTextView.getSelectionEnd());
updateDrawable();
- if (mSelectionActionMode != null) {
- mSelectionActionMode.invalidate();
+ if (mTextActionMode != null) {
+ mTextActionMode.invalidate();
}
}
@@ -4150,8 +4194,8 @@ public class Editor {
public void updateSelection(int offset) {
Selection.setSelection((Spannable) mTextView.getText(),
mTextView.getSelectionStart(), offset);
- if (mSelectionActionMode != null) {
- mSelectionActionMode.invalidate();
+ if (mTextActionMode != null) {
+ mTextActionMode.invalidate();
}
updateDrawable();
}
@@ -4515,7 +4559,7 @@ public class Editor {
mEndHandle.showAtLocation(endOffset);
// No longer the first dragging motion, reset.
- startSelectionActionModeWithSelection();
+ startSelectionActionMode();
mDragAcceleratorActive = false;
mStartOffset = -1;
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 3a85476..b68934b 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -1481,7 +1481,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
if (mEditor.hasSelectionController()) {
- mEditor.startSelectionActionModeWithSelection();
+ mEditor.startSelectionActionMode();
}
}
}
@@ -5282,7 +5282,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// - onFocusChanged cannot start it when focus is given to a view with selected text (after
// a screen rotation) since layout is not yet initialized at that point.
if (mEditor != null && mEditor.mCreatedWithASelection) {
- mEditor.startSelectionActionModeWithSelection();
+ mEditor.startSelectionActionMode();
mEditor.mCreatedWithASelection = false;
}
@@ -5290,7 +5290,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// ExtractEditText does not call onFocus when it is displayed, and mHasSelectionOnFocus can
// not be set. Do the test here instead.
if (this instanceof ExtractEditText && hasSelection() && mEditor != null) {
- mEditor.startSelectionActionModeWithSelection();
+ mEditor.startSelectionActionMode();
}
unregisterForPreDraw();
@@ -5908,7 +5908,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
- boolean isInSelectionMode = mEditor != null && mEditor.mSelectionActionMode != null;
+ boolean isInSelectionMode = mEditor != null && mEditor.mTextActionMode != null;
if (isInSelectionMode) {
if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
@@ -5923,7 +5923,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
state.handleUpEvent(event);
}
if (event.isTracking() && !event.isCanceled()) {
- stopSelectionActionMode();
+ stopTextActionMode();
return true;
}
}
@@ -6092,8 +6092,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// Has to be done on key down (and not on key up) to correctly be intercepted.
case KeyEvent.KEYCODE_BACK:
- if (mEditor != null && mEditor.mSelectionActionMode != null) {
- stopSelectionActionMode();
+ if (mEditor != null && mEditor.mTextActionMode != null) {
+ stopTextActionMode();
return -1;
}
break;
@@ -6423,7 +6423,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// extracted mode will start. Some text is selected though, and will trigger an action mode
// in the extracted view.
mEditor.hideControllers();
- stopSelectionActionMode();
+ stopTextActionMode();
}
/**
@@ -8258,7 +8258,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
super.onVisibilityChanged(changedView, visibility);
if (mEditor != null && visibility != VISIBLE) {
mEditor.hideControllers();
- stopSelectionActionMode();
+ stopTextActionMode();
}
}
@@ -8976,7 +8976,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
Selection.setSelection((Spannable) text, start, end);
// Make sure selection mode is engaged.
if (mEditor != null) {
- mEditor.startSelectionActionModeWithSelection();
+ mEditor.startSelectionActionMode();
}
return true;
}
@@ -9072,9 +9072,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
switch (id) {
case ID_SELECT_ALL:
- // This does not enter text selection mode. Text is highlighted, so that it can be
- // bulk edited, like selectAllOnFocus does. Returns true even if text is empty.
+ // This starts an action mode if triggered from another action mode. Text is
+ // highlighted, so that it can be bulk edited, like selectAllOnFocus does. Returns
+ // true even if text is empty.
+ boolean shouldRestartActionMode =
+ mEditor != null && mEditor.mTextActionMode != null;
+ stopTextActionMode();
selectAllText();
+ if (shouldRestartActionMode) {
+ mEditor.startSelectionActionMode();
+ }
return true;
case ID_UNDO:
@@ -9100,12 +9107,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
case ID_CUT:
setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
deleteText_internal(min, max);
- stopSelectionActionMode();
+ stopTextActionMode();
return true;
case ID_COPY:
setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
- stopSelectionActionMode();
+ stopTextActionMode();
return true;
case ID_REPLACE:
@@ -9195,14 +9202,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* selection is initiated in this View.
*
* The standard implementation populates the menu with a subset of Select All, Cut, Copy,
- * Paste and Share actions, depending on what this View supports.
+ * Paste, Replace and Share actions, depending on what this View supports.
*
* A custom implementation can add new entries in the default menu in its
* {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The
* default actions can also be removed from the menu using
* {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
- * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste} or
- * {@link android.R.id#shareText} ids as parameters.
+ * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
+ * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
*
* Returning false from
* {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent
@@ -9230,11 +9237,48 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
+ * If provided, this ActionMode.Callback will be used to create the ActionMode when text
+ * insertion is initiated in this View.
+ *
+ * The standard implementation populates the menu with a subset of Select All,
+ * Paste and Replace actions, depending on what this View supports.
+ *
+ * A custom implementation can add new entries in the default menu in its
+ * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The
+ * default actions can also be removed from the menu using
+ * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
+ * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.
+ *
+ * Returning false from
+ * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent
+ * the action mode from being started.
+ *
+ * Action click events should be handled by the custom implementation of
+ * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, MenuItem)}.
+ *
+ * Note that text insertion mode is not started when a TextView receives focus and the
+ * {@link android.R.attr#selectAllOnFocus} flag has been set.
+ */
+ public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) {
+ createEditorIfNeeded();
+ mEditor.mCustomInsertionActionModeCallback = actionModeCallback;
+ }
+
+ /**
+ * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null.
+ *
+ * @return The current custom insertion callback.
+ */
+ public ActionMode.Callback getCustomInsertionActionModeCallback() {
+ return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
+ }
+
+ /**
* @hide
*/
- protected void stopSelectionActionMode() {
+ protected void stopTextActionMode() {
if (mEditor != null) {
- mEditor.stopSelectionActionMode();
+ mEditor.stopTextActionMode();
}
}
@@ -9298,7 +9342,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
boolean canSelectAllText() {
- return canSelectText() && !hasPasswordTransformationMethod();
+ return canSelectText() && !hasPasswordTransformationMethod()
+ && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
}
boolean selectAllText() {
@@ -9346,7 +9391,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
}
- stopSelectionActionMode();
+ stopTextActionMode();
sLastCutCopyOrTextChangedTime = 0;
}
}
@@ -9359,7 +9404,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
getContext().startActivity(Intent.createChooser(sharingIntent, null));
- stopSelectionActionMode();
+ stopTextActionMode();
}
}
diff --git a/core/java/com/android/internal/view/FloatingActionMode.java b/core/java/com/android/internal/view/FloatingActionMode.java
index 93d2a1d..89cac4c 100644
--- a/core/java/com/android/internal/view/FloatingActionMode.java
+++ b/core/java/com/android/internal/view/FloatingActionMode.java
@@ -127,11 +127,16 @@ public class FloatingActionMode extends ActionMode {
private void repositionToolbar() {
checkToolbarInitialized();
+
+ mContentRectOnWindow.set(mContentRect);
+ mContentRectOnWindow.offset(mViewPosition[0], mViewPosition[1]);
+ // Make sure that content rect is not out of the view's visible bounds.
mContentRectOnWindow.set(
- mContentRect.left + mViewPosition[0],
- mContentRect.top + mViewPosition[1],
- mContentRect.right + mViewPosition[0],
- mContentRect.bottom + mViewPosition[1]);
+ Math.max(mContentRectOnWindow.left, mViewRect.left),
+ Math.max(mContentRectOnWindow.top, mViewRect.top),
+ Math.min(mContentRectOnWindow.right, mViewRect.right),
+ Math.min(mContentRectOnWindow.bottom, mViewRect.bottom));
+
if (!mContentRectOnWindow.equals(mPreviousContentRectOnWindow)) {
if (!mPreviousContentRectOnWindow.isEmpty()) {
notifyContentRectMoving();
diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java
index c32ba85..c77d614 100644
--- a/core/java/com/android/internal/widget/FloatingToolbar.java
+++ b/core/java/com/android/internal/widget/FloatingToolbar.java
@@ -84,7 +84,7 @@ public final class FloatingToolbar {
private final Rect mContentRect = new Rect();
private Menu mMenu;
- private List<CharSequence> mShowingTitles = new ArrayList<CharSequence>();
+ private List<Object> mShowingMenuItems = new ArrayList<Object>();
private MenuItem.OnMenuItemClickListener mMenuItemClickListener = NO_OP_MENUITEM_CLICK_LISTENER;
private int mSuggestedWidth;
@@ -156,7 +156,7 @@ public final class FloatingToolbar {
if (!isCurrentlyShowing(menuItems) || mWidthChanged) {
mPopup.dismiss();
mPopup.layoutMenuItems(menuItems, mMenuItemClickListener, mSuggestedWidth);
- mShowingTitles = getMenuItemTitles(menuItems);
+ mShowingMenuItems = getShowingMenuItemsReferences(menuItems);
}
mPopup.updateCoordinates(mContentRect);
if (!mPopup.isShowing()) {
@@ -211,7 +211,7 @@ public final class FloatingToolbar {
* Returns true if this floating toolbar is currently showing the specified menu items.
*/
private boolean isCurrentlyShowing(List<MenuItem> menuItems) {
- return mShowingTitles.equals(getMenuItemTitles(menuItems));
+ return mShowingMenuItems.equals(getShowingMenuItemsReferences(menuItems));
}
/**
@@ -234,12 +234,16 @@ public final class FloatingToolbar {
return menuItems;
}
- private List<CharSequence> getMenuItemTitles(List<MenuItem> menuItems) {
- List<CharSequence> titles = new ArrayList<CharSequence>();
+ private List<Object> getShowingMenuItemsReferences(List<MenuItem> menuItems) {
+ List<Object> references = new ArrayList<Object>();
for (MenuItem menuItem : menuItems) {
- titles.add(menuItem.getTitle());
+ if (isIconOnlyMenuItem(menuItem)) {
+ references.add(menuItem.getIcon());
+ } else {
+ references.add(menuItem.getTitle());
+ }
}
- return titles;
+ return references;
}