diff options
Diffstat (limited to 'core/java')
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; } |
