diff options
Diffstat (limited to 'core')
27 files changed, 804 insertions, 288 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/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/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..79d82a8 --- /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. + * + * @throw 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. + * + * @throw 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..be37293 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/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/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 a0d1930..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(); diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 56f9b5c..4c98abf 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -134,7 +134,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 +206,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 +238,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 +247,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 +312,7 @@ public class Editor { void replace() { int middle = (mTextView.getSelectionStart() + mTextView.getSelectionEnd()) / 2; - stopSelectionActionMode(); + stopTextActionMode(); Selection.setSelection((Spannable) mTextView.getText(), middle); showSuggestions(); } @@ -343,7 +345,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 +374,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 +392,7 @@ public class Editor { mPreserveDetachedSelection = true; hideControllers(); - stopSelectionActionMode(); + stopTextActionMode(); mPreserveDetachedSelection = false; mTemporaryDetach = false; } @@ -586,7 +588,7 @@ public class Editor { } if (!mSelectionControllerEnabled) { - stopSelectionActionMode(); + stopTextActionMode(); if (mSelectionModifierCursorController != null) { mSelectionModifierCursorController.onDetached(); mSelectionModifierCursorController = null; @@ -984,14 +986,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 +1003,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 +1103,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 +1151,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 +1218,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 +1231,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 +1239,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 +1703,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 +1726,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,13 +1745,13 @@ 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; @@ -1784,10 +1780,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 +1796,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 +1903,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 +1917,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 +1936,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 +2024,7 @@ public class Editor { mSuggestionsPopupWindow = new SuggestionsPopupWindow(); } hideControllers(); - stopSelectionActionMode(); + stopTextActionMode(); mSuggestionsPopupWindow.show(); } @@ -2035,8 +2032,8 @@ public class Editor { if (mPositionListener != null) { mPositionListener.onScrollChanged(); } - if (mSelectionActionMode != null) { - mSelectionActionMode.invalidateContentRect(); + if (mTextActionMode != null) { + mTextActionMode.invalidateContentRect(); } } @@ -3087,45 +3084,51 @@ 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)) { + Callback customCallback = getCustomCallback(); + if (customCallback != null) { + if (!customCallback.onCreateActionMode(mode, menu)) { // The custom mode can choose to cancel the action mode return false; } @@ -3141,6 +3144,12 @@ 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). @@ -3203,8 +3212,9 @@ public class Editor { public boolean onPrepareActionMode(ActionMode mode, Menu menu) { updateReplaceItem(menu); - if (mCustomSelectionActionModeCallback != null) { - return mCustomSelectionActionModeCallback.onPrepareActionMode(mode, menu); + Callback customCallback = getCustomCallback(); + if (customCallback != null) { + return customCallback.onPrepareActionMode(mode, menu); } return true; } @@ -3230,8 +3240,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 +3249,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 +3270,7 @@ public class Editor { mSelectionModifierCursorController.resetTouchOffsets(); } - mSelectionActionMode = null; + mTextActionMode = null; } @Override @@ -3274,7 +3285,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 +3296,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 +3306,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 +3858,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 +3945,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 +3983,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 +4035,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 +4161,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 +4526,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..6872caa 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; } @@ -9100,12 +9100,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 +9195,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 +9230,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(); } } @@ -9346,7 +9383,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } } - stopSelectionActionMode(); + stopTextActionMode(); sLastCutCopyOrTextChangedTime = 0; } } @@ -9359,7 +9396,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/jni/Android.mk b/core/jni/Android.mk index 40fee2c..faf926c 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -183,6 +183,7 @@ LOCAL_C_INCLUDES += \ external/pdfium/core/include/fpdfapi \ external/pdfium/core/include/fpdfdoc \ external/pdfium/fpdfsdk/include \ + external/pdfium/public \ external/skia/src/core \ external/skia/src/effects \ external/skia/src/images \ diff --git a/core/jni/android/graphics/pdf/PdfEditor.cpp b/core/jni/android/graphics/pdf/PdfEditor.cpp index 84434ae..52b69e0 100644 --- a/core/jni/android/graphics/pdf/PdfEditor.cpp +++ b/core/jni/android/graphics/pdf/PdfEditor.cpp @@ -20,8 +20,8 @@ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" #include "fpdfview.h" -#include "fpdfedit.h" -#include "fpdfsave.h" +#include "fpdf_edit.h" +#include "fpdf_save.h" #include "fsdk_rendercontext.h" #include "fpdf_transformpage.h" #pragma GCC diagnostic pop @@ -58,7 +58,7 @@ static int sUnmatchedInitRequestCount = 0; static void initializeLibraryIfNeeded() { Mutex::Autolock _l(sLock); if (sUnmatchedInitRequestCount == 0) { - FPDF_InitLibrary(NULL); + FPDF_InitLibrary(); } sUnmatchedInitRequestCount++; } diff --git a/core/jni/android/graphics/pdf/PdfRenderer.cpp b/core/jni/android/graphics/pdf/PdfRenderer.cpp index 876bea4..006eef8 100644 --- a/core/jni/android/graphics/pdf/PdfRenderer.cpp +++ b/core/jni/android/graphics/pdf/PdfRenderer.cpp @@ -50,7 +50,7 @@ static int sUnmatchedInitRequestCount = 0; static void initializeLibraryIfNeeded() { Mutex::Autolock _l(sLock); if (sUnmatchedInitRequestCount == 0) { - FPDF_InitLibrary(NULL); + FPDF_InitLibrary(); } sUnmatchedInitRequestCount++; } @@ -165,12 +165,12 @@ static void renderPageBitmap(FPDF_BITMAP bitmap, FPDF_PAGE page, int destLeft, i // and FPDF_ANNOT flags. To add support for that refer to FPDF_RenderPage_Retail // in fpdfview.cpp - CRenderContext* pContext = FX_NEW CRenderContext; + CRenderContext* pContext = new CRenderContext; CPDF_Page* pPage = (CPDF_Page*) page; pPage->SetPrivateData((void*) 1, pContext, DropContext); - CFX_FxgeDevice* fxgeDevice = FX_NEW CFX_FxgeDevice; + CFX_FxgeDevice* fxgeDevice = new CFX_FxgeDevice; pContext->m_pDevice = fxgeDevice; // Reverse the bytes (last argument TRUE) since the Android @@ -180,7 +180,7 @@ static void renderPageBitmap(FPDF_BITMAP bitmap, FPDF_PAGE page, int destLeft, i CPDF_RenderOptions* renderOptions = pContext->m_pOptions; if (!renderOptions) { - renderOptions = FX_NEW CPDF_RenderOptions; + renderOptions = new CPDF_RenderOptions; pContext->m_pOptions = renderOptions; } @@ -205,7 +205,7 @@ static void renderPageBitmap(FPDF_BITMAP bitmap, FPDF_PAGE page, int destLeft, i clip.bottom = destBottom; fxgeDevice->SetClip_Rect(&clip); - CPDF_RenderContext* pageContext = FX_NEW CPDF_RenderContext; + CPDF_RenderContext* pageContext = new CPDF_RenderContext; pContext->m_pContext = pageContext; pageContext->Create(pPage); @@ -232,7 +232,7 @@ static void renderPageBitmap(FPDF_BITMAP bitmap, FPDF_PAGE page, int destLeft, i } pageContext->AppendObjectList(pPage, &matrix); - pContext->m_pRenderer = FX_NEW CPDF_ProgressiveRenderer; + pContext->m_pRenderer = new CPDF_ProgressiveRenderer; pContext->m_pRenderer->Start(pageContext, fxgeDevice, renderOptions, NULL); fxgeDevice->RestoreState(); |
