diff options
Diffstat (limited to 'core/java/android')
22 files changed, 1290 insertions, 186 deletions
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 88b1f2d..a0a6c4c 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -754,6 +754,14 @@ public class DevicePolicyManager { public static final int FLAG_MANAGED_CAN_ACCESS_PARENT = 0x0002; /** + * Broadcast action: notify that a new local OTA policy has been set by the device owner. + * The new policy can be retrieved by {@link #getOtaPolicy()}. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_OTA_POLICY_CHANGED = "android.app.action.OTA_POLICY_CHANGED"; + + + /** * Return true if the given administrator component is currently * active (enabled) in the system. */ @@ -3254,6 +3262,73 @@ public class DevicePolicyManager { } /** + * Called by a profile owner of a managed profile to set whether bluetooth + * devices can access enterprise contacts. + * <p> + * The calling device admin must be a profile owner. If it is not, a + * security exception will be thrown. + * <p> + * This API works on managed profile only. + * + * @param who Which {@link DeviceAdminReceiver} this request is associated + * with. + * @param disabled If true, bluetooth devices cannot access enterprise + * contacts. + */ + public void setBluetoothContactSharingDisabled(ComponentName who, boolean disabled) { + if (mService != null) { + try { + mService.setBluetoothContactSharingDisabled(who, disabled); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + } + + /** + * Called by a profile owner of a managed profile to determine whether or + * not Bluetooth devices cannot access enterprise contacts. + * <p> + * The calling device admin must be a profile owner. If it is not, a + * security exception will be thrown. + * <p> + * This API works on managed profile only. + * + * @param who Which {@link DeviceAdminReceiver} this request is associated + * with. + */ + public boolean getBluetoothContactSharingDisabled(ComponentName who) { + if (mService != null) { + try { + return mService.getBluetoothContactSharingDisabled(who); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + return true; + } + + /** + * Determine whether or not Bluetooth devices cannot access contacts. + * <p> + * This API works on managed profile UserHandle only. + * + * @param userHandle The user for whom to check the caller-id permission + * @hide + */ + public boolean getBluetoothContactSharingDisabled(UserHandle userHandle) { + if (mService != null) { + try { + return mService.getBluetoothContactSharingDisabledForUser(userHandle + .getIdentifier()); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + return true; + } + + /** * Called by the profile owner of a managed profile so that some intents sent in the managed * profile can also be resolved in the parent, or vice versa. * Only activity intents are supported. @@ -4067,4 +4142,72 @@ public class DevicePolicyManager { Log.w(TAG, "Could not send device initializer status", re); } } + + /* + * Called by device owners to set a local OTA update policy. When a new OTA policy is set, + * {@link #ACTION_OTA_POLICY_CHANGED} is broadcasted. + * + * @param who Which {@link DeviceAdminReceiver} this request is associated with. All components + * in the device owner package can set OTA policies and the most recent policy takes effect. + * @param policy the new OTA policy, or null to clear the current policy. + * @see OtaPolicy + */ + public void setOtaPolicy(ComponentName who, OtaPolicy policy) { + if (mService != null) { + try { + if (policy != null) { + mService.setOtaPolicy(who, policy.getPolicyBundle()); + } else { + mService.setOtaPolicy(who, null); + } + } catch (RemoteException re) { + Log.w(TAG, "Error calling setOtaPolicy", re); + } + } + } + + /** + * Retrieve a local OTA update policy set previously by {@link #setOtaPolicy}. + * + * @return The current OTA policy object, or null if no policy is set or the system does not + * support managed OTA. + */ + public OtaPolicy getOtaPolicy() { + if (mService != null) { + try { + PersistableBundle bundle = mService.getOtaPolicy(); + if (bundle != null) { + return new OtaPolicy(bundle); + } else { + return null; + } + } catch (RemoteException re) { + Log.w(TAG, "Error calling getOtaPolicy", re); + } + } + return null; + } + + /** + * Called by a device owner to disable the keyguard altogether. + * + * <p>Setting the keyguard to disabled has the same effect as choosing "None" as the screen + * lock type. However, this call has no effect if a password, pin or pattern is currently set. + * If a password, pin or pattern is set after the keyguard was disabled, the keyguard stops + * being disabled. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param enabled New state of the keyguard. + * + * @return {@code false} if attempting to disable the keyguard while a lock password was in + * place. {@code true} otherwise." + */ + public boolean setKeyguardEnabledState(ComponentName admin, boolean enabled) { + try { + return mService.setKeyguardEnabledState(admin, enabled); + } catch (RemoteException re) { + Log.w(TAG, "Failed talking with device policy service", re); + return false; + } + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 75b97a8..131b99c 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -191,6 +191,10 @@ interface IDevicePolicyManager { boolean getCrossProfileCallerIdDisabledForUser(int userId); void startManagedQuickContact(String lookupKey, long contactId, in Intent originalIntent); + void setBluetoothContactSharingDisabled(in ComponentName who, boolean disabled); + boolean getBluetoothContactSharingDisabled(in ComponentName who); + boolean getBluetoothContactSharingDisabledForUser(int userId); + void setTrustAgentConfiguration(in ComponentName admin, in ComponentName agent, in PersistableBundle args); List<PersistableBundle> getTrustAgentConfiguration(in ComponentName admin, @@ -215,4 +219,8 @@ interface IDevicePolicyManager { void setUserIcon(in ComponentName admin, in Bitmap icon); void sendDeviceInitializerStatus(int statusCode, String description); + void setOtaPolicy(in ComponentName who, in PersistableBundle policy); + PersistableBundle getOtaPolicy(); + + boolean setKeyguardEnabledState(in ComponentName admin, boolean enabled); } diff --git a/core/java/android/app/admin/OtaPolicy.java b/core/java/android/app/admin/OtaPolicy.java new file mode 100644 index 0000000..98581a7 --- /dev/null +++ b/core/java/android/app/admin/OtaPolicy.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.admin; + +import android.annotation.IntDef; +import android.os.PersistableBundle; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * A class that represents a local OTA policy set by the device owner. + * + * @see DevicePolicyManager#setOtaPolicy + * @see DevicePolicyManager#getOtaPolicy + */ +public class OtaPolicy { + + /** @hide */ + @IntDef({ + TYPE_INSTALL_AUTOMATIC, + TYPE_INSTALL_WINDOWED, + TYPE_POSTPONE}) + @Retention(RetentionPolicy.SOURCE) + @interface OtaPolicyType {} + + /** + * Install OTA update automatically as soon as one is available. + */ + public static final int TYPE_INSTALL_AUTOMATIC = 1; + + /** + * Install OTA update automatically within a daily maintenance window, for a maximum of two-week + * period. After that period the OTA will be installed automatically. + */ + public static final int TYPE_INSTALL_WINDOWED = 2; + + /** + * Incoming OTA will be blocked for a maximum of two weeks, after which it will be installed + * automatically. + */ + public static final int TYPE_POSTPONE = 3; + + private static final String KEY_POLICY_TYPE = "policy_type"; + private static final String KEY_INSTALL_WINDOW_START = "install_window_start"; + private static final String KEY_INSTALL_WINDOW_END = "install_window_end"; + + private PersistableBundle mPolicy; + + public OtaPolicy() { + mPolicy = new PersistableBundle(); + } + + /** + * Construct an OtaPolicy object from a bundle. + * @hide + */ + public OtaPolicy(PersistableBundle in) { + mPolicy = new PersistableBundle(in); + } + + /** + * Retrieve the underlying bundle where the policy is stored. + * @hide + */ + public PersistableBundle getPolicyBundle() { + return new PersistableBundle(mPolicy); + } + + /** + * Set the OTA policy to: install OTA update automatically as soon as one is available. + */ + public void setAutomaticInstallPolicy() { + mPolicy.clear(); + mPolicy.putInt(KEY_POLICY_TYPE, TYPE_INSTALL_AUTOMATIC); + } + + /** + * Set the OTA policy to: new OTA update will only be installed automatically when the system + * clock is inside a daily maintenance window. If the start and end times are the same, the + * window is considered to include the WHOLE 24 hours, that is, OTAs can install at any time. If + * the given window in invalid, a {@link OtaPolicy.InvalidWindowException} will be thrown. If + * start time is later than end time, the window is considered spanning midnight, i.e. end time + * donates a time on the next day. The maintenance window will last for two weeks, after which + * the OTA will be installed automatically. + * + * @param startTime the start of the maintenance window, measured as the number of minutes from + * midnight in the device's local time. Must be in the range of [0, 1440). + * @param endTime the end of the maintenance window, measured as the number of minutes from + * midnight in the device's local time. Must be in the range of [0, 1440). + */ + public void setWindowedInstallPolicy(int startTime, int endTime) throws InvalidWindowException{ + if (startTime < 0 || startTime >= 1440 || endTime < 0 || endTime >= 1440) { + throw new InvalidWindowException("startTime and endTime must be inside [0, 1440)"); + } + mPolicy.clear(); + mPolicy.putInt(KEY_POLICY_TYPE, TYPE_INSTALL_WINDOWED); + mPolicy.putInt(KEY_INSTALL_WINDOW_START, startTime); + mPolicy.putInt(KEY_INSTALL_WINDOW_END, endTime); + } + + /** + * Set the OTA policy to: block installation for a maximum period of two weeks. After the + * block expires the OTA will be installed automatically. + */ + public void setPostponeInstallPolicy() { + mPolicy.clear(); + mPolicy.putInt(KEY_POLICY_TYPE, TYPE_POSTPONE); + } + + /** + * Returns the type of OTA policy. + * + * @return an integer, either one of {@link #TYPE_INSTALL_AUTOMATIC}, + * {@link #TYPE_INSTALL_WINDOWED} and {@link #TYPE_POSTPONE}, or -1 if no policy has been set. + */ + @OtaPolicyType + public int getPolicyType() { + return mPolicy.getInt(KEY_POLICY_TYPE, -1); + } + + /** + * Get the start of the maintenance window. + * + * @return the start of the maintenance window measured as the number of minutes from midnight, + * or -1 if the policy does not have a maintenance window. + */ + public int getInstallWindowStart() { + if (getPolicyType() == TYPE_INSTALL_WINDOWED) { + return mPolicy.getInt(KEY_INSTALL_WINDOW_START, -1); + } else { + return -1; + } + } + + /** + * Get the end of the maintenance window. + * + * @return the end of the maintenance window measured as the number of minutes from midnight, + * or -1 if the policy does not have a maintenance window. + */ + public int getInstallWindowEnd() { + if (getPolicyType() == TYPE_INSTALL_WINDOWED) { + return mPolicy.getInt(KEY_INSTALL_WINDOW_END, -1); + } else { + return -1; + } + } + + @Override + public String toString() { + return mPolicy.toString(); + } + + /** + * Exception thrown by {@link OtaPolicy#setWindowedInstallPolicy(int, int)} in case the + * specified window is invalid. + */ + public static class InvalidWindowException extends Exception { + public InvalidWindowException(String reason) { + super(reason); + } + } +} + diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java index ce83028..6b6f026 100644 --- a/core/java/android/hardware/camera2/CameraCaptureSession.java +++ b/core/java/android/hardware/camera2/CameraCaptureSession.java @@ -17,21 +17,31 @@ package android.hardware.camera2; import android.os.Handler; +import android.view.Surface; import java.util.List; + /** - * A configured capture session for a {@link CameraDevice}, used for capturing - * images from the camera. + * A configured capture session for a {@link CameraDevice}, used for capturing images from the + * camera or reprocessing images captured from the camera in the same session previously. * * <p>A CameraCaptureSession is created by providing a set of target output surfaces to - * {@link CameraDevice#createCaptureSession createCaptureSession}. Once created, the session is - * active until a new session is created by the camera device, or the camera device is closed.</p> + * {@link CameraDevice#createCaptureSession createCaptureSession}, or by providing an + * {@link android.hardware.camera2.params.InputConfiguration} and a set of target output surfaces to + * {@link CameraDevice#createReprocessibleCaptureSession createReprocessibleCaptureSession} for a + * reprocessible capture session. Once created, the session is active until a new session is + * created by the camera device, or the camera device is closed.</p> + * + * <p>All capture sessions can be used for capturing images from the camera but only reprocessible + * capture sessions can reprocess images captured from the camera in the same session previously. + * </p> * * <p>Creating a session is an expensive operation and can take several hundred milliseconds, since * it requires configuring the camera device's internal pipelines and allocating memory buffers for * sending images to the desired targets. Therefore the setup is done asynchronously, and - * {@link CameraDevice#createCaptureSession createCaptureSession} will send the ready-to-use - * CameraCaptureSession to the provided listener's + * {@link CameraDevice#createCaptureSession createCaptureSession} and + * {@link CameraDevice#createReprocessibleCaptureSession createReprocessibleCaptureSession} will + * send the ready-to-use CameraCaptureSession to the provided listener's * {@link CameraCaptureSession.StateCallback#onConfigured onConfigured} callback. If configuration * cannot be completed, then the * {@link CameraCaptureSession.StateCallback#onConfigureFailed onConfigureFailed} is called, and the @@ -77,6 +87,12 @@ public abstract class CameraCaptureSession implements AutoCloseable { * {@link #setRepeatingBurst}, and will be processed as soon as the current * repeat/repeatBurst processing completes.</p> * + * <p>All capture sessions can be used for capturing images from the camera but only capture + * sessions created by + * {@link CameraDevice#createReprocessibleCaptureSession createReprocessibleCaptureSession} + * can submit reprocess capture requests. Submitting a reprocess request to a regular capture + * session will result in an {@link IllegalArgumentException}.</p> + * * @param request the settings for this capture * @param listener The callback object to notify once this request has been * processed. If null, no metadata will be produced for this capture, @@ -94,7 +110,9 @@ public abstract class CameraCaptureSession implements AutoCloseable { * was explicitly closed, a new session has been created * or the camera device has been closed. * @throws IllegalArgumentException if the request targets no Surfaces or Surfaces that are not - * configured as outputs for this session. Or if the handler is + * configured as outputs for this session. Or if a reprocess + * capture request is submitted in a non-reprocessible capture + * session. Or if the handler is * null, the listener is not null, and the calling thread has * no looper. * @@ -102,6 +120,7 @@ public abstract class CameraCaptureSession implements AutoCloseable { * @see #setRepeatingRequest * @see #setRepeatingBurst * @see #abortCaptures + * @see CameraDevice#createReprocessibleCaptureSession */ public abstract int capture(CaptureRequest request, CaptureCallback listener, Handler handler) throws CameraAccessException; @@ -121,6 +140,13 @@ public abstract class CameraCaptureSession implements AutoCloseable { * {@link #capture} repeatedly is that this method guarantees that no * other requests will be interspersed with the burst.</p> * + * <p>All capture sessions can be used for capturing images from the camera but only capture + * sessions created by + * {@link CameraDevice#createReprocessibleCaptureSession createReprocessibleCaptureSession} + * can submit reprocess capture requests. The list of requests must all be capturing images from + * the camera or all be reprocess capture requests. Submitting a reprocess request to a regular + * capture session will result in an {@link IllegalArgumentException}.</p> + * * @param requests the list of settings for this burst capture * @param listener The callback object to notify each time one of the * requests in the burst has been processed. If null, no metadata will be @@ -138,9 +164,13 @@ public abstract class CameraCaptureSession implements AutoCloseable { * @throws IllegalStateException if this session is no longer active, either because the session * was explicitly closed, a new session has been created * or the camera device has been closed. - * @throws IllegalArgumentException If the requests target no Surfaces or Surfaces not currently - * configured as outputs. Or if the handler is null, the - * listener is not null, and the calling thread has no looper. + * @throws IllegalArgumentException If the requests target no Surfaces, or target Surfaces not + * currently configured as outputs. Or if a reprocess + * capture request is submitted in a non-reprocessible capture + * session. Or if the list of requests contains both requests + * to capture images from the camera and reprocess capture + * requests. Or if the handler is null, the listener is not + * null, and the calling thread has no looper. * * @see #capture * @see #setRepeatingRequest @@ -175,6 +205,14 @@ public abstract class CameraCaptureSession implements AutoCloseable { * in-progress burst will be completed before the new repeat request will be * used.</p> * + * <p>This method does not support reprocess capture requests because each reprocess + * {@link CaptureRequest} must be created from the {@link TotalCaptureResult} that matches + * the input image to be reprocessed. This is either the {@link TotalCaptureResult} of capture + * that is sent for reprocessing, or one of the {@link TotalCaptureResult TotalCaptureResults} + * of a set of captures, when data from the whole set is combined by the application into a + * single reprocess input image. The request must be capturing images from the camera. If a + * reprocess capture request is submitted, this method will throw IllegalArgumentException.</p> + * * @param request the request to repeat indefinitely * @param listener The callback object to notify every time the * request finishes processing. If null, no metadata will be @@ -193,9 +231,10 @@ public abstract class CameraCaptureSession implements AutoCloseable { * was explicitly closed, a new session has been created * or the camera device has been closed. * @throws IllegalArgumentException If the requests reference no Surfaces or Surfaces that are - * not currently configured as outputs. Or if the handler is - * null, the listener is not null, and the calling thread has - * no looper. Or if no requests were passed in. + * not currently configured as outputs. Or if the request is + * a reprocess capture request. Or if the handler is null, the + * listener is not null, and the calling thread has no looper. + * Or if no requests were passed in. * * @see #capture * @see #captureBurst @@ -235,6 +274,14 @@ public abstract class CameraCaptureSession implements AutoCloseable { * in-progress burst will be completed before the new repeat burst will be * used.</p> * + * <p>This method does not support reprocess capture requests because each reprocess + * {@link CaptureRequest} must be created from the {@link TotalCaptureResult} that matches + * the input image to be reprocessed. This is either the {@link TotalCaptureResult} of capture + * that is sent for reprocessing, or one of the {@link TotalCaptureResult TotalCaptureResults} + * of a set of captures, when data from the whole set is combined by the application into a + * single reprocess input image. The request must be capturing images from the camera. If a + * reprocess capture request is submitted, this method will throw IllegalArgumentException.</p> + * * @param requests the list of requests to cycle through indefinitely * @param listener The callback object to notify each time one of the * requests in the repeating bursts has finished processing. If null, no @@ -253,7 +300,8 @@ public abstract class CameraCaptureSession implements AutoCloseable { * was explicitly closed, a new session has been created * or the camera device has been closed. * @throws IllegalArgumentException If the requests reference no Surfaces or Surfaces not - * currently configured as outputs. Or if the handler is null, + * currently configured as outputs. Or if one of the requests + * is a reprocess capture request. Or if the handler is null, * the listener is not null, and the calling thread has no * looper. Or if no requests were passed in. * @@ -298,9 +346,10 @@ public abstract class CameraCaptureSession implements AutoCloseable { * request or a repeating burst is set, it will be cleared.</p> * * <p>This method is the fastest way to switch the camera device to a new session with - * {@link CameraDevice#createCaptureSession}, at the cost of discarding in-progress work. It - * must be called before the new session is created. Once all pending requests are either - * completed or thrown away, the {@link StateCallback#onReady} callback will be called, + * {@link CameraDevice#createCaptureSession} or + * {@link CameraDevice#createReprocessibleCaptureSession}, at the cost of discarding in-progress + * work. It must be called before the new session is created. Once all pending requests are + * either completed or thrown away, the {@link StateCallback#onReady} callback will be called, * if the session has not been closed. Otherwise, the {@link StateCallback#onClosed} * callback will be fired when a new session is created by the camera device.</p> * @@ -321,10 +370,39 @@ public abstract class CameraCaptureSession implements AutoCloseable { * @see #setRepeatingRequest * @see #setRepeatingBurst * @see CameraDevice#createCaptureSession + * @see CameraDevice#createReprocessibleCaptureSession */ public abstract void abortCaptures() throws CameraAccessException; /** + * Return if the application can submit reprocess capture requests with this camera capture + * session. + * + * @return {@code true} if the application can submit reprocess capture requests with this + * camera capture session. {@code false} otherwise. + * + * @see CameraDevice#createReprocessibleCaptureSession + */ + public abstract boolean isReprocessible(); + + /** + * Get the input Surface associated with a reprocessible capture session. + * + * <p>Each reprocessible capture session has an input {@link Surface} where the reprocess + * capture requests get the input images from, rather than the camera device. The application + * can create a {@link android.media.ImageWriter} with this input {@link Surface} and use it to + * provide input images for reprocess capture requests.</p> + * + * @return The {@link Surface} where reprocessing capture requests get the input images from. If + * this is not a reprocess capture session, {@code null} will be returned. + * + * @see CameraDevice#createReprocessibleCaptureSession + * @see android.media.ImageWriter + * @see android.media.ImageReader + */ + public abstract Surface getInputSurface(); + + /** * Close this capture session asynchronously. * * <p>Closing a session frees up the target output Surfaces of the session for reuse with either diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index fd4cf3c..51b326b 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -16,6 +16,7 @@ package android.hardware.camera2; +import android.hardware.camera2.params.InputConfiguration; import android.hardware.camera2.params.StreamConfigurationMap; import android.hardware.camera2.params.OutputConfiguration; import android.os.Handler; @@ -135,7 +136,7 @@ public abstract class CameraDevice implements AutoCloseable { * * <p>The active capture session determines the set of potential output Surfaces for * the camera device for each capture request. A given request may use all - * or a only some of the outputs. Once the CameraCaptureSession is created, requests can be + * or only some of the outputs. Once the CameraCaptureSession is created, requests can be * can be submitted with {@link CameraCaptureSession#capture capture}, * {@link CameraCaptureSession#captureBurst captureBurst}, * {@link CameraCaptureSession#setRepeatingRequest setRepeatingRequest}, or @@ -393,6 +394,75 @@ public abstract class CameraDevice implements AutoCloseable { List<OutputConfiguration> outputConfigurations, CameraCaptureSession.StateCallback callback, Handler handler) throws CameraAccessException; + /** + * Create a new reprocessible camera capture session by providing the desired reprocessing + * input Surface configuration and the target output set of Surfaces to the camera device. + * + * <p>If a camera device supports YUV reprocessing + * ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING}) or OPAQUE + * reprocessing + * ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_OPAQUE_REPROCESSING}), besides + * the capture session created via {@link #createCaptureSession}, the application can also + * create a reprocessible capture session to submit reprocess capture requests in addition to + * regular capture requests. A reprocess capture request takes the next available buffer from + * the session's input Surface, and sends it through the camera device's processing pipeline + * again, to produce buffers for the request's target output Surfaces. No new image data is + * captured for a reprocess request. However the input buffer provided by + * the application must be captured previously by the same camera device in the same session + * directly (e.g. for Zero-Shutter-Lag use case) or indirectly (e.g. combining multiple output + * images).</p> + * + * <p>The active reprocessible capture session determines an input {@link Surface} and the set + * of potential output Surfaces for the camera devices for each capture request. The application + * can use {@link #createCaptureRequest} to create regular capture requests to capture new + * images from the camera device, and use {@link #createReprocessCaptureRequest} to create + * reprocess capture requests to process buffers from the input {@link Surface}. A request may + * use all or only some of the outputs. All the output Surfaces in one capture request will come + * from the same source, either from a new capture by the camera device, or from the input + * Surface depending on if the request is a reprocess capture request.</p> + * + * <p>Input formats and sizes supported by the camera device can be queried via + * {@link StreamConfigurationMap#getInputFormats} and + * {@link StreamConfigurationMap#getInputSizes}. For each supported input format, the camera + * device supports a set of output formats and sizes for reprocessing that can be queried via + * {@link StreamConfigurationMap#getValidOutputFormatsForInput} and + * {@link StreamConfigurationMap#getOutputSizes}. While output Surfaces with formats that + * aren't valid reprocess output targets for the input configuration can be part of a session, + * they cannot be used as targets for a reprocessing request.</p> + * + * <p>Since the application cannot access {@link android.graphics.ImageFormat#PRIVATE} images + * directly, an output Surface created by {@link android.media.ImageReader#newOpaqueInstance} + * will be considered as intended to be used for reprocessing input and thus the + * {@link android.media.ImageReader} size must match one of the supported input sizes for + * {@link android.graphics.ImageFormat#PRIVATE} format. Otherwise, creating a reprocessible + * capture session will fail.</p> + * + * @param inputConfig The configuration for the input {@link Surface} + * @param outputs The new set of Surfaces that should be made available as + * targets for captured image data. + * @param callback The callback to notify about the status of the new capture session. + * @param handler The handler on which the callback should be invoked, or {@code null} to use + * the current thread's {@link android.os.Looper looper}. + * + * @throws IllegalArgumentException if the input configuration is null or not supported, the set + * of output Surfaces do not meet the requirements, the + * callback is null, or the handler is null but the current + * thread has no looper. + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @throws IllegalStateException if the camera device has been closed + * + * @see CameraCaptureSession + * @see StreamConfigurationMap#getInputFormats + * @see StreamConfigurationMap#getInputSizes + * @see StreamConfigurationMap#getValidOutputFormatsForInput + * @see StreamConfigurationMap#getOutputSizes + * @see android.media.ImageWriter + * @see android.media.ImageReader + */ + public abstract void createReprocessibleCaptureSession(InputConfiguration inputConfig, + List<Surface> outputs, CameraCaptureSession.StateCallback callback, Handler handler) + throws CameraAccessException; /** * <p>Create a {@link CaptureRequest.Builder} for new capture requests, @@ -423,6 +493,36 @@ public abstract class CameraDevice implements AutoCloseable { throws CameraAccessException; /** + * <p>Create a {@link CaptureRequest.Builder} for a new reprocess {@link CaptureRequest} from a + * {@link TotalCaptureResult}. + * + * <p>Each reprocess {@link CaptureRequest} processes one buffer from + * {@link CameraCaptureSession}'s input {@link Surface} to all output {@link Surface Surfaces} + * included in the reprocess capture request. The reprocess input images must be generated from + * one or multiple output images captured from the same camera device. The application can + * provide input images to camera device via + * {{@link android.media.ImageWriter#queueInputImage ImageWriter#queueInputImage}}. + * The application must use the capture result of one of those output images to create a + * reprocess capture request so that the camera device can use the information to achieve + * optimal reprocess image quality. + * + * @param inputResult The capture result of the output image or one of the output images used + * to generate the reprocess input image for this capture request. + * + * @throws IllegalArgumentException if inputResult is null. + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @throws IllegalStateException if the camera device has been closed + * + * @see CaptureRequest.Builder + * @see TotalCaptureResult + * @see CameraDevice#createReprocessibleCaptureSession + * @see android.media.ImageWriter + */ + public abstract CaptureRequest.Builder createReprocessCaptureRequest( + TotalCaptureResult inputResult) throws CameraAccessException; + + /** * Close the connection to this camera device as quickly as possible. * * <p>Immediately after this call, all calls to the camera device or active session interface diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index b513379..1a00a05 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -148,7 +148,7 @@ public final class CameraManager { * new one provided.</p> * * <p>The first time a callback is registered, it is immediately called - * with the torch mode status of all currently known camera devices.</p> + * with the torch mode status of all currently known camera devices with a flash unit.</p> * * <p>Since this callback will be registered with the camera service, remember to unregister it * once it is no longer needed; otherwise the callback will continue to receive events @@ -524,7 +524,7 @@ public final class CameraManager { * A callback for camera flash torch modes becoming unavailable, disabled, or enabled. * * <p>The torch mode becomes unavailable when the camera device it belongs to becomes - * unavailable or other camera resouces it needs become busy due to other higher priority + * unavailable or other camera resources it needs become busy due to other higher priority * camera activities. The torch mode becomes disabled when it was turned off or when the camera * device it belongs to is no longer in use and other camera resources it needs are no longer * busy. A camera's torch mode is turned off when an application calls {@link #setTorchMode} to diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index b8fb8e7..35727e8 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -157,6 +157,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> private final HashSet<Surface> mSurfaceSet; private final CameraMetadataNative mSettings; + private boolean mIsReprocess; private Object mUserTag; @@ -168,6 +169,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> private CaptureRequest() { mSettings = new CameraMetadataNative(); mSurfaceSet = new HashSet<Surface>(); + mIsReprocess = false; } /** @@ -179,6 +181,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> private CaptureRequest(CaptureRequest source) { mSettings = new CameraMetadataNative(source.mSettings); mSurfaceSet = (HashSet<Surface>) source.mSurfaceSet.clone(); + mIsReprocess = source.mIsReprocess; mUserTag = source.mUserTag; } @@ -187,9 +190,10 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * * Used by the Builder to create a mutable CaptureRequest. */ - private CaptureRequest(CameraMetadataNative settings) { + private CaptureRequest(CameraMetadataNative settings, boolean isReprocess) { mSettings = CameraMetadataNative.move(settings); mSurfaceSet = new HashSet<Surface>(); + mIsReprocess = isReprocess; } /** @@ -257,10 +261,27 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> } /** + * Determine if this is a reprocess capture request. + * + * <p>A reprocess capture request produces output images from an input buffer from the + * {@link CameraCaptureSession}'s input {@link Surface}. A reprocess capture request can be + * created by {@link CameraDevice#createReprocessCaptureRequest}.</p> + * + * @return {@code true} if this is a reprocess capture request. {@code false} if this is not a + * reprocess capture request. + * + * @see CameraDevice#createReprocessCaptureRequest + */ + public boolean isReprocess() { + return mIsReprocess; + } + + /** * Determine whether this CaptureRequest is equal to another CaptureRequest. * * <p>A request is considered equal to another is if it's set of key/values is equal, it's - * list of output surfaces is equal, and the user tag is equal.</p> + * list of output surfaces is equal, the user tag is equal, and the return values of + * isReprocess() are equal.</p> * * @param other Another instance of CaptureRequest. * @@ -276,7 +297,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> return other != null && Objects.equals(mUserTag, other.mUserTag) && mSurfaceSet.equals(other.mSurfaceSet) - && mSettings.equals(other.mSettings); + && mSettings.equals(other.mSettings) + && mIsReprocess == other.mIsReprocess; } @Override @@ -323,6 +345,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> Surface s = (Surface) p; mSurfaceSet.add(s); } + + mIsReprocess = (in.readInt() == 0) ? false : true; } @Override @@ -334,6 +358,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> public void writeToParcel(Parcel dest, int flags) { mSettings.writeToParcel(dest, flags); dest.writeParcelableArray(mSurfaceSet.toArray(new Surface[mSurfaceSet.size()]), flags); + dest.writeInt(mIsReprocess ? 1 : 0); } /** @@ -374,8 +399,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * * @hide */ - public Builder(CameraMetadataNative template) { - mRequest = new CaptureRequest(template); + public Builder(CameraMetadataNative template, boolean reprocess) { + mRequest = new CaptureRequest(template, reprocess); } /** diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index d8f92e5..4134d28 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -295,11 +295,18 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>Whenever a request has been processed, regardless of failure or success, * it gets a unique frame number assigned to its future result/failure.</p> * - * <p>This value monotonically increments, starting with 0, - * for every new result or failure; and the scope is the lifetime of the - * {@link CameraDevice}.</p> + * <p>For the same type of request (capturing from the camera device or reprocessing), this + * value monotonically increments, starting with 0, for every new result or failure and the + * scope is the lifetime of the {@link CameraDevice}. Between different types of requests, + * the frame number may not monotonically increment. For example, the frame number of a newer + * reprocess result may be smaller than the frame number of an older result of capturing new + * images from the camera device, but the frame number of a newer reprocess result will never be + * smaller than the frame number of an older reprocess result.</p> * * @return The frame number + * + * @see CameraDevice#createCaptureRequest + * @see CameraDevice#createReprocessCaptureRequest */ public long getFrameNumber() { return mFrameNumber; diff --git a/core/java/android/hardware/camera2/ICameraDeviceUser.aidl b/core/java/android/hardware/camera2/ICameraDeviceUser.aidl index 01f2396..23bfa66 100644 --- a/core/java/android/hardware/camera2/ICameraDeviceUser.aidl +++ b/core/java/android/hardware/camera2/ICameraDeviceUser.aidl @@ -16,11 +16,11 @@ package android.hardware.camera2; -import android.hardware.camera2.params.OutputConfiguration; -import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.CaptureRequest; - +import android.hardware.camera2.impl.CameraMetadataNative; +import android.hardware.camera2.params.OutputConfiguration; import android.hardware.camera2.utils.LongParcelable; +import android.view.Surface; /** @hide */ interface ICameraDeviceUser @@ -68,6 +68,29 @@ interface ICameraDeviceUser // non-negative value is the stream ID. negative value is status_t int createStream(in OutputConfiguration outputConfiguration); + /** + * Create an input stream + * + * <p>Create an input stream of width, height, and format</p> + * + * @param width Width of the input buffers + * @param height Height of the input buffers + * @param format Format of the input buffers. One of HAL_PIXEL_FORMAT_*. + * + * @return stream ID if it's a non-negative value. status_t if it's a negative value. + */ + int createInputStream(int width, int height, int format); + + /** + * Get the surface of the input stream. + * + * <p>It's valid to call this method only after a stream configuration is completed + * successfully and the stream configuration includes a input stream.</p> + * + * @param surface An output argument for the surface of the input stream buffer queue. + */ + int getInputSurface(out Surface surface); + int createDefaultRequest(int templateId, out CameraMetadataNative request); int getCameraInfo(out CameraMetadataNative info); diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java index e87a2f8..fb5b13c 100644 --- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java @@ -44,6 +44,8 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { private final int mId; private final String mIdString; + /** Input surface configured by native camera framework based on user-specified configuration */ + private final Surface mInput; /** User-specified set of surfaces used as the configuration outputs */ private final List<Surface> mOutputs; /** @@ -85,7 +87,7 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { * There must be no pending actions * (e.g. no pending captures, no repeating requests, no flush).</p> */ - CameraCaptureSessionImpl(int id, List<Surface> outputs, + CameraCaptureSessionImpl(int id, Surface input, List<Surface> outputs, CameraCaptureSession.StateCallback callback, Handler stateHandler, android.hardware.camera2.impl.CameraDeviceImpl deviceImpl, Handler deviceStateHandler, boolean configureSuccess) { @@ -100,6 +102,7 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { // TODO: extra verification of outputs mOutputs = outputs; + mInput = input; mStateHandler = checkHandler(stateHandler); mStateCallback = createUserStateCallbackProxy(mStateHandler, callback); @@ -145,8 +148,12 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { Handler handler) throws CameraAccessException { if (request == null) { throw new IllegalArgumentException("request must not be null"); + } else if (request.isReprocess() && !isReprocessible()) { + throw new IllegalArgumentException("this capture session cannot handle reprocess " + + "requests"); } + checkNotClosed(); handler = checkHandler(handler, callback); @@ -169,6 +176,19 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { throw new IllegalArgumentException("requests must have at least one element"); } + boolean reprocess = requests.get(0).isReprocess(); + if (reprocess && !isReprocessible()) { + throw new IllegalArgumentException("this capture session cannot handle reprocess " + + "requests"); + } + + for (int i = 1; i < requests.size(); i++) { + if (requests.get(i).isReprocess() != reprocess) { + throw new IllegalArgumentException("cannot mix regular and reprocess capture " + + " requests"); + } + } + checkNotClosed(); handler = checkHandler(handler, callback); @@ -188,8 +208,11 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { Handler handler) throws CameraAccessException { if (request == null) { throw new IllegalArgumentException("request must not be null"); + } else if (request.isReprocess()) { + throw new IllegalArgumentException("repeating reprocess requests are not supported"); } + checkNotClosed(); handler = checkHandler(handler, callback); @@ -212,6 +235,13 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { throw new IllegalArgumentException("requests must have at least one element"); } + for (CaptureRequest r : requests) { + if (r.isReprocess()) { + throw new IllegalArgumentException("repeating reprocess burst requests are not " + + "supported"); + } + } + checkNotClosed(); handler = checkHandler(handler, callback); @@ -257,6 +287,16 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { // The next BUSY -> IDLE set of transitions will mark the end of the abort. } + @Override + public boolean isReprocessible() { + return mInput != null; + } + + @Override + public Surface getInputSurface() { + return mInput; + } + /** * Replace this session with another session. * @@ -658,8 +698,8 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { mUnconfigureDrainer.taskStarted(); try { - mDeviceImpl - .configureOutputsChecked(null); // begin transition to unconfigured + // begin transition to unconfigured + mDeviceImpl.configureStreamsChecked(null, null); } catch (CameraAccessException e) { // OK: do not throw checked exceptions. Log.e(TAG, mIdString + "Exception while configuring outputs: ", e); diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index 38f8e39..91388c3 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -28,7 +28,10 @@ import android.hardware.camera2.CaptureFailure; import android.hardware.camera2.ICameraDeviceCallbacks; import android.hardware.camera2.ICameraDeviceUser; import android.hardware.camera2.TotalCaptureResult; +import android.hardware.camera2.params.InputConfiguration; import android.hardware.camera2.params.OutputConfiguration; +import android.hardware.camera2.params.ReprocessFormatsMap; +import android.hardware.camera2.params.StreamConfigurationMap; import android.hardware.camera2.utils.CameraBinderDecorator; import android.hardware.camera2.utils.CameraRuntimeException; import android.hardware.camera2.utils.LongParcelable; @@ -37,6 +40,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.util.Log; +import android.util.Size; import android.util.SparseArray; import android.view.Surface; @@ -46,7 +50,8 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.TreeSet; +import java.util.LinkedList; +import java.util.TreeMap; /** * HAL2.1+ implementation of CameraDevice. Use CameraManager#open to instantiate @@ -78,9 +83,11 @@ public class CameraDeviceImpl extends CameraDevice { private int mRepeatingRequestId = REQUEST_ID_NONE; private final ArrayList<Integer> mRepeatingRequestIdDeletedList = new ArrayList<Integer>(); - // Map stream IDs to Surfaces + // Map stream IDs to input/output configurations + private SimpleEntry<Integer, InputConfiguration> mConfiguredInput = + new SimpleEntry<>(REQUEST_ID_NONE, null); private final SparseArray<OutputConfiguration> mConfiguredOutputs = - new SparseArray<OutputConfiguration>(); + new SparseArray<>(); private final String mCameraId; private final CameraCharacteristics mCharacteristics; @@ -320,38 +327,48 @@ public class CameraDeviceImpl extends CameraDevice { for (Surface s : outputs) { outputConfigs.add(new OutputConfiguration(s)); } - configureOutputsChecked(outputConfigs); + configureStreamsChecked(/*inputConfig*/null, outputConfigs); + } /** - * Attempt to configure the outputs; the device goes to idle and then configures the - * new outputs if possible. + * Attempt to configure the input and outputs; the device goes to idle and then configures the + * new input and outputs if possible. * - * <p>The configuration may gracefully fail, if there are too many outputs, if the formats - * are not supported, or if the sizes for that format is not supported. In this case this - * function will return {@code false} and the unconfigured callback will be fired.</p> + * <p>The configuration may gracefully fail, if input configuration is not supported, + * if there are too many outputs, if the formats are not supported, or if the sizes for that + * format is not supported. In this case this function will return {@code false} and the + * unconfigured callback will be fired.</p> * - * <p>If the configuration succeeds (with 1 or more outputs), then the idle callback is fired. - * Unconfiguring the device always fires the idle callback.</p> + * <p>If the configuration succeeds (with 1 or more outputs with or without an input), + * then the idle callback is fired. Unconfiguring the device always fires the idle callback.</p> * + * @param inputConfig input configuration or {@code null} for no input * @param outputs a list of one or more surfaces, or {@code null} to unconfigure * @return whether or not the configuration was successful * * @throws CameraAccessException if there were any unexpected problems during configuration */ - public boolean configureOutputsChecked(List<OutputConfiguration> outputs) - throws CameraAccessException { + public boolean configureStreamsChecked(InputConfiguration inputConfig, + List<OutputConfiguration> outputs) throws CameraAccessException { // Treat a null input the same an empty list if (outputs == null) { outputs = new ArrayList<OutputConfiguration>(); } + if (outputs.size() == 0 && inputConfig != null) { + throw new IllegalArgumentException("cannot configure an input stream without " + + "any output streams"); + } + + checkInputConfiguration(inputConfig); + boolean success = false; synchronized(mInterfaceLock) { checkIfCameraClosedOrInError(); // Streams to create HashSet<OutputConfiguration> addSet = new HashSet<OutputConfiguration>(outputs); - // Streams to delete + // Streams to delete List<Integer> deleteList = new ArrayList<Integer>(); // Determine which streams need to be created, which to be deleted @@ -373,6 +390,24 @@ public class CameraDeviceImpl extends CameraDevice { waitUntilIdle(); mRemoteDevice.beginConfigure(); + + // reconfigure the input stream if the input configuration is different. + InputConfiguration currentInputConfig = mConfiguredInput.getValue(); + if (inputConfig != currentInputConfig && + (inputConfig == null || !inputConfig.equals(currentInputConfig))) { + if (currentInputConfig != null) { + mRemoteDevice.deleteStream(mConfiguredInput.getKey()); + mConfiguredInput = new SimpleEntry<Integer, InputConfiguration>( + REQUEST_ID_NONE, null); + } + if (inputConfig != null) { + int streamId = mRemoteDevice.createInputStream(inputConfig.getWidth(), + inputConfig.getHeight(), inputConfig.getFormat()); + mConfiguredInput = new SimpleEntry<Integer, InputConfiguration>( + streamId, inputConfig); + } + } + // Delete all streams first (to free up HW resources) for (Integer streamId : deleteList) { mRemoteDevice.deleteStream(streamId); @@ -429,7 +464,7 @@ public class CameraDeviceImpl extends CameraDevice { for (Surface surface : outputs) { outConfigurations.add(new OutputConfiguration(surface)); } - createCaptureSessionByOutputConfiguration(outConfigurations, callback, handler); + createCaptureSessionInternal(null, outConfigurations, callback, handler); } @Override @@ -437,9 +472,39 @@ public class CameraDeviceImpl extends CameraDevice { List<OutputConfiguration> outputConfigurations, CameraCaptureSession.StateCallback callback, Handler handler) throws CameraAccessException { + if (DEBUG) { + Log.d(TAG, "createCaptureSessionByOutputConfiguration"); + } + + createCaptureSessionInternal(null, outputConfigurations, callback, handler); + } + + @Override + public void createReprocessibleCaptureSession(InputConfiguration inputConfig, + List<Surface> outputs, CameraCaptureSession.StateCallback callback, Handler handler) + throws CameraAccessException { + if (DEBUG) { + Log.d(TAG, "createReprocessibleCaptureSession"); + } + + if (inputConfig == null) { + throw new IllegalArgumentException("inputConfig cannot be null when creating a " + + "reprocessible capture session"); + } + List<OutputConfiguration> outConfigurations = new ArrayList<>(outputs.size()); + for (Surface surface : outputs) { + outConfigurations.add(new OutputConfiguration(surface)); + } + createCaptureSessionInternal(inputConfig, outConfigurations, callback, handler); + } + + private void createCaptureSessionInternal(InputConfiguration inputConfig, + List<OutputConfiguration> outputConfigurations, + CameraCaptureSession.StateCallback callback, Handler handler) + throws CameraAccessException { synchronized(mInterfaceLock) { if (DEBUG) { - Log.d(TAG, "createCaptureSession"); + Log.d(TAG, "createCaptureSessionInternal"); } checkIfCameraClosedOrInError(); @@ -453,15 +518,24 @@ public class CameraDeviceImpl extends CameraDevice { // TODO: dont block for this boolean configureSuccess = true; CameraAccessException pendingException = null; + Surface input = null; try { - // configure outputs and then block until IDLE - configureSuccess = configureOutputsChecked(outputConfigurations); + // configure streams and then block until IDLE + configureSuccess = configureStreamsChecked(inputConfig, outputConfigurations); + if (inputConfig != null) { + input = new Surface(); + mRemoteDevice.getInputSurface(/*out*/input); + } } catch (CameraAccessException e) { configureSuccess = false; pendingException = e; + input = null; if (DEBUG) { Log.v(TAG, "createCaptureSession - failed with exception ", e); } + } catch (RemoteException e) { + // impossible + return; } List<Surface> outSurfaces = new ArrayList<>(outputConfigurations.size()); @@ -470,7 +544,7 @@ public class CameraDeviceImpl extends CameraDevice { } // Fire onConfigured if configureOutputs succeeded, fire onConfigureFailed otherwise. CameraCaptureSessionImpl newSession = - new CameraCaptureSessionImpl(mNextSessionId++, + new CameraCaptureSessionImpl(mNextSessionId++, input, outSurfaces, callback, handler, this, mDeviceHandler, configureSuccess); @@ -512,12 +586,25 @@ public class CameraDeviceImpl extends CameraDevice { } CaptureRequest.Builder builder = - new CaptureRequest.Builder(templatedRequest); + new CaptureRequest.Builder(templatedRequest, /*reprocess*/false); return builder; } } + @Override + public CaptureRequest.Builder createReprocessCaptureRequest(TotalCaptureResult inputResult) + throws CameraAccessException { + synchronized(mInterfaceLock) { + checkIfCameraClosedOrInError(); + + CameraMetadataNative resultMetadata = new + CameraMetadataNative(inputResult.getNativeCopy()); + + return new CaptureRequest.Builder(resultMetadata, /*reprocess*/true); + } + } + public int capture(CaptureRequest request, CaptureCallback callback, Handler handler) throws CameraAccessException { if (DEBUG) { @@ -810,6 +897,40 @@ public class CameraDeviceImpl extends CameraDevice { } } + private void checkInputConfiguration(InputConfiguration inputConfig) { + if (inputConfig != null) { + StreamConfigurationMap configMap = mCharacteristics.get( + CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + + int[] inputFormats = configMap.getInputFormats(); + boolean validFormat = false; + for (int format : inputFormats) { + if (format == inputConfig.getFormat()) { + validFormat = true; + } + } + + if (validFormat == false) { + throw new IllegalArgumentException("input format " + inputConfig.getFormat() + + " is not valid"); + } + + boolean validSize = false; + Size[] inputSizes = configMap.getInputSizes(inputConfig.getFormat()); + for (Size s : inputSizes) { + if (inputConfig.getWidth() == s.getWidth() && + inputConfig.getHeight() == s.getHeight()) { + validSize = true; + } + } + + if (validSize == false) { + throw new IllegalArgumentException("input size " + inputConfig.getWidth() + "x" + + inputConfig.getHeight() + " is not valid"); + } + } + } + /** * <p>A callback for tracking the progress of a {@link CaptureRequest} * submitted to the camera device.</p> @@ -996,19 +1117,46 @@ public class CameraDeviceImpl extends CameraDevice { public class FrameNumberTracker { private long mCompletedFrameNumber = -1; - private final TreeSet<Long> mFutureErrorSet = new TreeSet<Long>(); + private long mCompletedReprocessFrameNumber = -1; + /** the skipped frame numbers that belong to regular results */ + private final LinkedList<Long> mSkippedRegularFrameNumbers = new LinkedList<Long>(); + /** the skipped frame numbers that belong to reprocess results */ + private final LinkedList<Long> mSkippedReprocessFrameNumbers = new LinkedList<Long>(); + /** frame number -> is reprocess */ + private final TreeMap<Long, Boolean> mFutureErrorMap = new TreeMap<Long, Boolean>(); /** Map frame numbers to list of partial results */ private final HashMap<Long, List<CaptureResult>> mPartialResults = new HashMap<>(); private void update() { - Iterator<Long> iter = mFutureErrorSet.iterator(); + Iterator iter = mFutureErrorMap.entrySet().iterator(); while (iter.hasNext()) { - long errorFrameNumber = iter.next(); - if (errorFrameNumber == mCompletedFrameNumber + 1) { - mCompletedFrameNumber++; - iter.remove(); + TreeMap.Entry pair = (TreeMap.Entry)iter.next(); + Long errorFrameNumber = (Long)pair.getKey(); + Boolean reprocess = (Boolean)pair.getValue(); + Boolean removeError = true; + if (reprocess) { + if (errorFrameNumber == mCompletedReprocessFrameNumber + 1) { + mCompletedReprocessFrameNumber = errorFrameNumber; + } else if (mSkippedReprocessFrameNumbers.isEmpty() != true && + errorFrameNumber == mSkippedReprocessFrameNumbers.element()) { + mCompletedReprocessFrameNumber = errorFrameNumber; + mSkippedReprocessFrameNumbers.remove(); + } else { + removeError = false; + } } else { - break; + if (errorFrameNumber == mCompletedFrameNumber + 1) { + mCompletedFrameNumber = errorFrameNumber; + } else if (mSkippedRegularFrameNumbers.isEmpty() != true && + errorFrameNumber == mSkippedRegularFrameNumbers.element()) { + mCompletedFrameNumber = errorFrameNumber; + mSkippedRegularFrameNumbers.remove(); + } else { + removeError = false; + } + } + if (removeError) { + iter.remove(); } } } @@ -1017,25 +1165,21 @@ public class CameraDeviceImpl extends CameraDevice { * This function is called every time when a result or an error is received. * @param frameNumber the frame number corresponding to the result or error * @param isError true if it is an error, false if it is not an error + * @param isReprocess true if it is a reprocess result, false if it is a regular result. */ - public void updateTracker(long frameNumber, boolean isError) { + public void updateTracker(long frameNumber, boolean isError, boolean isReprocess) { if (isError) { - mFutureErrorSet.add(frameNumber); + mFutureErrorMap.put(frameNumber, isReprocess); } else { - /** - * HAL cannot send an OnResultReceived for frame N unless it knows for - * sure that all frames prior to N have either errored out or completed. - * So if the current frame is not an error, then all previous frames - * should have arrived. The following line checks whether this holds. - */ - if (frameNumber != mCompletedFrameNumber + 1) { - Log.e(TAG, String.format( - "result frame number %d comes out of order, should be %d + 1", - frameNumber, mCompletedFrameNumber)); - // Continue on to set the completed frame number to this frame anyway, - // to be robust to lower-level errors and allow for clean shutdowns. + try { + if (isReprocess) { + updateCompletedReprocessFrameNumber(frameNumber); + } else { + updateCompletedFrameNumber(frameNumber); + } + } catch (IllegalArgumentException e) { + Log.e(TAG, e.getMessage()); } - mCompletedFrameNumber = frameNumber; } update(); } @@ -1049,12 +1193,13 @@ public class CameraDeviceImpl extends CameraDevice { * @param frameNumber the frame number corresponding to the result * @param result the total or partial result * @param partial {@true} if the result is partial, {@code false} if total + * @param isReprocess true if it is a reprocess result, false if it is a regular result. */ - public void updateTracker(long frameNumber, CaptureResult result, boolean partial) { - + public void updateTracker(long frameNumber, CaptureResult result, boolean partial, + boolean isReprocess) { if (!partial) { // Update the total result's frame status as being successful - updateTracker(frameNumber, /*isError*/false); + updateTracker(frameNumber, /*isError*/false, isReprocess); // Don't keep a list of total results, we don't need to track them return; } @@ -1093,28 +1238,112 @@ public class CameraDeviceImpl extends CameraDevice { return mCompletedFrameNumber; } + public long getCompletedReprocessFrameNumber() { + return mCompletedReprocessFrameNumber; + } + + /** + * Update the completed frame number for regular results. + * + * It validates that all previous frames have arrived except for reprocess frames. + * + * If there is a gap since previous regular frame number, assume the frames in the gap are + * reprocess frames and store them in the skipped reprocess frame number queue to check + * against when reprocess frames arrive. + */ + private void updateCompletedFrameNumber(long frameNumber) throws IllegalArgumentException { + if (frameNumber <= mCompletedFrameNumber) { + throw new IllegalArgumentException("frame number " + frameNumber + " is a repeat"); + } else if (frameNumber <= mCompletedReprocessFrameNumber) { + // if frame number is smaller than completed reprocess frame number, + // it must be the head of mSkippedRegularFrameNumbers + if (mSkippedRegularFrameNumbers.isEmpty() == true || + frameNumber < mSkippedRegularFrameNumbers.element()) { + throw new IllegalArgumentException("frame number " + frameNumber + + " is a repeat"); + } else if (frameNumber > mSkippedRegularFrameNumbers.element()) { + throw new IllegalArgumentException("frame number " + frameNumber + + " comes out of order. Expecting " + + mSkippedRegularFrameNumbers.element()); + } + // frame number matches the head of the skipped frame number queue. + mSkippedRegularFrameNumbers.remove(); + } else { + // there is a gap of unseen frame numbers which should belong to reprocess result + // put all the skipped frame numbers in the queue + for (long i = Math.max(mCompletedFrameNumber, mCompletedReprocessFrameNumber) + 1; + i < frameNumber; i++) { + mSkippedReprocessFrameNumbers.add(i); + } + } + + mCompletedFrameNumber = frameNumber; + } + + /** + * Update the completed frame number for reprocess results. + * + * It validates that all previous frames have arrived except for regular frames. + * + * If there is a gap since previous reprocess frame number, assume the frames in the gap are + * regular frames and store them in the skipped regular frame number queue to check + * against when regular frames arrive. + */ + private void updateCompletedReprocessFrameNumber(long frameNumber) + throws IllegalArgumentException { + if (frameNumber < mCompletedReprocessFrameNumber) { + throw new IllegalArgumentException("frame number " + frameNumber + " is a repeat"); + } else if (frameNumber < mCompletedFrameNumber) { + // if reprocess frame number is smaller than completed regular frame number, + // it must be the head of the skipped reprocess frame number queue. + if (mSkippedReprocessFrameNumbers.isEmpty() == true || + frameNumber < mSkippedReprocessFrameNumbers.element()) { + throw new IllegalArgumentException("frame number " + frameNumber + + " is a repeat"); + } else if (frameNumber > mSkippedReprocessFrameNumbers.element()) { + throw new IllegalArgumentException("frame number " + frameNumber + + " comes out of order. Expecting " + + mSkippedReprocessFrameNumbers.element()); + } + // frame number matches the head of the skipped frame number queue. + mSkippedReprocessFrameNumbers.remove(); + } else { + // put all the skipped frame numbers in the queue + for (long i = Math.max(mCompletedFrameNumber, mCompletedReprocessFrameNumber) + 1; + i < frameNumber; i++) { + mSkippedRegularFrameNumbers.add(i); + } + } + mCompletedReprocessFrameNumber = frameNumber; + } } private void checkAndFireSequenceComplete() { long completedFrameNumber = mFrameNumberTracker.getCompletedFrameNumber(); + long completedReprocessFrameNumber = mFrameNumberTracker.getCompletedReprocessFrameNumber(); + boolean isReprocess = false; Iterator<SimpleEntry<Long, Integer> > iter = mFrameNumberRequestPairs.iterator(); while (iter.hasNext()) { final SimpleEntry<Long, Integer> frameNumberRequestPair = iter.next(); - if (frameNumberRequestPair.getKey() <= completedFrameNumber) { - - // remove request from mCaptureCallbackMap - final int requestId = frameNumberRequestPair.getValue(); - final CaptureCallbackHolder holder; - synchronized(mInterfaceLock) { - if (mRemoteDevice == null) { - Log.w(TAG, "Camera closed while checking sequences"); - return; - } + boolean sequenceCompleted = false; + final int requestId = frameNumberRequestPair.getValue(); + final CaptureCallbackHolder holder; + synchronized(mInterfaceLock) { + if (mRemoteDevice == null) { + Log.w(TAG, "Camera closed while checking sequences"); + return; + } - int index = mCaptureCallbackMap.indexOfKey(requestId); - holder = (index >= 0) ? mCaptureCallbackMap.valueAt(index) - : null; - if (holder != null) { + int index = mCaptureCallbackMap.indexOfKey(requestId); + holder = (index >= 0) ? + mCaptureCallbackMap.valueAt(index) : null; + if (holder != null) { + isReprocess = holder.getRequest().isReprocess(); + // check if it's okay to remove request from mCaptureCallbackMap + if ((isReprocess && frameNumberRequestPair.getKey() <= + completedReprocessFrameNumber) || (!isReprocess && + frameNumberRequestPair.getKey() <= completedFrameNumber)) { + sequenceCompleted = true; mCaptureCallbackMap.removeAt(index); if (DEBUG) { Log.v(TAG, String.format( @@ -1125,36 +1354,40 @@ public class CameraDeviceImpl extends CameraDevice { } } } + } + + // If no callback is registered for this requestId or sequence completed, remove it + // from the frame number->request pair because it's not needed anymore. + if (holder == null || sequenceCompleted) { iter.remove(); + } - // Call onCaptureSequenceCompleted - if (holder != null) { - Runnable resultDispatch = new Runnable() { - @Override - public void run() { - if (!CameraDeviceImpl.this.isClosed()){ - if (DEBUG) { - Log.d(TAG, String.format( - "fire sequence complete for request %d", - requestId)); - } + // Call onCaptureSequenceCompleted + if (sequenceCompleted) { + Runnable resultDispatch = new Runnable() { + @Override + public void run() { + if (!CameraDeviceImpl.this.isClosed()){ + if (DEBUG) { + Log.d(TAG, String.format( + "fire sequence complete for request %d", + requestId)); + } - long lastFrameNumber = frameNumberRequestPair.getKey(); - if (lastFrameNumber < Integer.MIN_VALUE - || lastFrameNumber > Integer.MAX_VALUE) { - throw new AssertionError(lastFrameNumber - + " cannot be cast to int"); - } - holder.getCallback().onCaptureSequenceCompleted( - CameraDeviceImpl.this, - requestId, - lastFrameNumber); + long lastFrameNumber = frameNumberRequestPair.getKey(); + if (lastFrameNumber < Integer.MIN_VALUE + || lastFrameNumber > Integer.MAX_VALUE) { + throw new AssertionError(lastFrameNumber + + " cannot be cast to int"); } + holder.getCallback().onCaptureSequenceCompleted( + CameraDeviceImpl.this, + requestId, + lastFrameNumber); } - }; - holder.getHandler().post(resultDispatch); - } - + } + }; + holder.getHandler().post(resultDispatch); } } } @@ -1319,9 +1552,11 @@ public class CameraDeviceImpl extends CameraDevice { final CaptureCallbackHolder holder = CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId); + final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId()); boolean isPartialResult = (resultExtras.getPartialResultCount() < mTotalPartialCount); + boolean isReprocess = request.isReprocess(); // Check if we have a callback for this if (holder == null) { @@ -1331,7 +1566,8 @@ public class CameraDeviceImpl extends CameraDevice { + frameNumber); } - mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult); + mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult, + isReprocess); return; } @@ -1343,11 +1579,11 @@ public class CameraDeviceImpl extends CameraDevice { + frameNumber); } - mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult); + mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult, + isReprocess); return; } - final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId()); Runnable resultDispatch = null; @@ -1398,7 +1634,7 @@ public class CameraDeviceImpl extends CameraDevice { holder.getHandler().post(resultDispatch); // Collect the partials for a total result; or mark the frame as totally completed - mFrameNumberTracker.updateTracker(frameNumber, finalResult, isPartialResult); + mFrameNumberTracker.updateTracker(frameNumber, finalResult, isPartialResult, isReprocess); // Fire onCaptureSequenceCompleted if (!isPartialResult) { @@ -1460,7 +1696,7 @@ public class CameraDeviceImpl extends CameraDevice { if (DEBUG) { Log.v(TAG, String.format("got error frame %d", frameNumber)); } - mFrameNumberTracker.updateTracker(frameNumber, /*error*/true); + mFrameNumberTracker.updateTracker(frameNumber, /*error*/true, request.isReprocess()); checkAndFireSequenceComplete(); } diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java index 70f3463..4cd9414 100644 --- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java +++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java @@ -530,6 +530,18 @@ public class CameraDeviceUserShim implements ICameraDeviceUser { } @Override + public int createInputStream(int width, int height, int format) { + Log.e(TAG, "creating input stream is not supported on legacy devices"); + return CameraBinderDecorator.INVALID_OPERATION; + } + + @Override + public int getInputSurface(/*out*/ Surface surface) { + Log.e(TAG, "getting input surface is not supported on legacy devices"); + return CameraBinderDecorator.INVALID_OPERATION; + } + + @Override public int createDefaultRequest(int templateId, /*out*/CameraMetadataNative request) { if (DEBUG) { Log.d(TAG, "createDefaultRequest called."); diff --git a/core/java/android/hardware/camera2/params/InputConfiguration.java b/core/java/android/hardware/camera2/params/InputConfiguration.java new file mode 100644 index 0000000..dea1c5c --- /dev/null +++ b/core/java/android/hardware/camera2/params/InputConfiguration.java @@ -0,0 +1,115 @@ +/* + * 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.params; + +import android.hardware.camera2.utils.HashCodeHelpers; + +/** + * Immutable class to store an input configuration that is used to create a reprocessible capture + * session. + * + * @see CameraDevice#createReprocessibleCaptureSession + * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP + */ +public final class InputConfiguration { + + private final int mWidth; + private final int mHeight; + private final int mFormat; + + /** + * Create an input configration with the width, height, and user-defined format. + * + * <p>Images of an user-defined format are accessible by applications. Use + * {@link android.hardware.camera2.CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP} + * to query supported input formats</p> + * + * @param width Width of the input buffers. + * @param height Height of the input buffers. + * @param format Format of the input buffers. One of ImageFormat or PixelFormat constants. + * + * @see android.graphics.ImageFormat + * @see android.graphics.PixelFormat + * @see android.hardware.camera2.CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP + */ + public InputConfiguration(int width, int height, int format) { + mWidth = width; + mHeight = height; + mFormat = format; + } + + /** + * Get the width of this input configration. + * + * @return width of this input configuration. + */ + public int getWidth() { + return mWidth; + } + + /** + * Get the height of this input configration. + * + * @return height of this input configuration. + */ + public int getHeight() { + return mHeight; + } + + /** + * Get the format of this input configration. + * + * @return format of this input configuration. + */ + public int getFormat() { + return mFormat; + } + + /** + * Check if this InputConfiguration is equal to another InputConfiguration. + * + * <p>Two input configurations are equal if and only if they have the same widths, heights, and + * formats.</p> + * + * @param obj the object to compare this instance with. + * + * @return {@code true} if the objects were equal, {@code false} otherwise. + */ + @Override + public boolean equals(Object obj) { + if (!(obj instanceof InputConfiguration)) { + return false; + } + + InputConfiguration otherInputConfig = (InputConfiguration) obj; + + if (otherInputConfig.getWidth() == mWidth && + otherInputConfig.getHeight() == mHeight && + otherInputConfig.getFormat() == mFormat) { + return true; + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return HashCodeHelpers.hashCode(mWidth, mHeight, mFormat); + } +} diff --git a/core/java/android/hardware/location/IFusedLocationHardware.aidl b/core/java/android/hardware/location/IFusedLocationHardware.aidl index 382c12c..3de766a 100644 --- a/core/java/android/hardware/location/IFusedLocationHardware.aidl +++ b/core/java/android/hardware/location/IFusedLocationHardware.aidl @@ -32,21 +32,21 @@ interface IFusedLocationHardware { * * @param eventSink The sink to register. */ - void registerSink(in IFusedLocationHardwareSink eventSink); + void registerSink(in IFusedLocationHardwareSink eventSink) = 0; /** * Unregisters a sink with the Location Hardware object. * * @param eventSink The sink to unregister. */ - void unregisterSink(in IFusedLocationHardwareSink eventSink); + void unregisterSink(in IFusedLocationHardwareSink eventSink) = 1; /** * Provides access to the batch size available in Hardware. * * @return The batch size the hardware supports. */ - int getSupportedBatchSize(); + int getSupportedBatchSize() = 2; /** * Requests the Hardware to start batching locations. @@ -56,7 +56,7 @@ interface IFusedLocationHardware { * * @throws RuntimeException if the request Id exists. */ - void startBatching(in int id, in FusedBatchOptions batchOptions); + void startBatching(in int id, in FusedBatchOptions batchOptions) = 3; /** * Requests the Hardware to stop batching for the given Id. @@ -64,7 +64,7 @@ interface IFusedLocationHardware { * @param id The request that needs to be stopped. * @throws RuntimeException if the request Id is unknown. */ - void stopBatching(in int id); + void stopBatching(in int id) = 4; /** * Updates a batching operation in progress. @@ -74,7 +74,7 @@ interface IFusedLocationHardware { * * @throws RuntimeException if the Id of the request is unknown. */ - void updateBatchingOptions(in int id, in FusedBatchOptions batchOptions); + void updateBatchingOptions(in int id, in FusedBatchOptions batchOptions) = 5; /** * Requests the most recent locations available in Hardware. @@ -83,14 +83,14 @@ interface IFusedLocationHardware { * * @param batchSizeRequested The number of locations requested. */ - void requestBatchOfLocations(in int batchSizeRequested); + void requestBatchOfLocations(in int batchSizeRequested) = 6; /** * Flags if the Hardware supports injection of diagnostic data. * * @return True if data injection is supported, false otherwise. */ - boolean supportsDiagnosticDataInjection(); + boolean supportsDiagnosticDataInjection() = 7; /** * Injects diagnostic data into the Hardware subsystem. @@ -98,14 +98,14 @@ interface IFusedLocationHardware { * @param data The data to inject. * @throws RuntimeException if injection is not supported. */ - void injectDiagnosticData(in String data); + void injectDiagnosticData(in String data) = 8; /** * Flags if the Hardware supports injection of device context information. * * @return True if device context injection is supported, false otherwise. */ - boolean supportsDeviceContextInjection(); + boolean supportsDeviceContextInjection() = 9; /** * Injects device context information into the Hardware subsystem. @@ -113,5 +113,12 @@ interface IFusedLocationHardware { * @param deviceEnabledContext The context to inject. * @throws RuntimeException if injection is not supported. */ - void injectDeviceContext(in int deviceEnabledContext); + void injectDeviceContext(in int deviceEnabledContext) = 10; + + /** + * Requests all batched locations currently available in Hardware + * and clears the buffer. Any subsequent calls will not return any + * of the locations returned in this call. + */ + void flushBatchedLocations() = 11; } diff --git a/core/java/android/hardware/location/IFusedLocationHardwareSink.aidl b/core/java/android/hardware/location/IFusedLocationHardwareSink.aidl index 2107ae8..c99cb0c 100644 --- a/core/java/android/hardware/location/IFusedLocationHardwareSink.aidl +++ b/core/java/android/hardware/location/IFusedLocationHardwareSink.aidl @@ -44,4 +44,10 @@ interface IFusedLocationHardwareSink { * capabilities. Should be called immediatly after init. */ void onCapabilities(int capabilities) = 2; + + /** + * Event generated from FLP HAL when the status of location batching + * changes (location is successful/unsuccessful). + */ + void onStatusChanged(int status) = 3; }
\ No newline at end of file diff --git a/core/java/android/net/IpPrefix.java b/core/java/android/net/IpPrefix.java index b268986..6b4f2d5 100644 --- a/core/java/android/net/IpPrefix.java +++ b/core/java/android/net/IpPrefix.java @@ -170,6 +170,21 @@ public final class IpPrefix implements Parcelable { } /** + * Determines whether the prefix contains the specified address. + * + * @param address An {@link InetAddress} to test. + * @return {@code true} if the prefix covers the given address. + */ + public boolean contains(InetAddress address) { + byte[] addrBytes = (address == null) ? null : address.getAddress(); + if (addrBytes == null || addrBytes.length != this.address.length) { + return false; + } + NetworkUtils.maskRawAddress(addrBytes, prefixLength); + return Arrays.equals(this.address, addrBytes); + } + + /** * Returns a string representation of this {@code IpPrefix}. * * @return a string such as {@code "192.0.2.0/24"} or {@code "2001:db8:1:2::/64"}. diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java index cfd20a0..90a2460 100644 --- a/core/java/android/net/RouteInfo.java +++ b/core/java/android/net/RouteInfo.java @@ -367,13 +367,7 @@ public final class RouteInfo implements Parcelable { * @return {@code true} if the destination and prefix length cover the given address. */ public boolean matches(InetAddress destination) { - if (destination == null) return false; - - // match the route destination and destination with prefix length - InetAddress dstNet = NetworkUtils.getNetworkPart(destination, - mDestination.getPrefixLength()); - - return mDestination.getAddress().equals(dstNet); + return mDestination.contains(destination); } /** diff --git a/core/java/android/service/gatekeeper/IGateKeeperService.aidl b/core/java/android/service/gatekeeper/IGateKeeperService.aidl new file mode 100644 index 0000000..2f3e296 --- /dev/null +++ b/core/java/android/service/gatekeeper/IGateKeeperService.aidl @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.gatekeeper; + +/** + * Interface for communication with GateKeeper, the + * secure password storage daemon. + * + * This must be kept manually in sync with system/core/gatekeeperd + * until AIDL can generate both C++ and Java bindings. + * + * @hide + */ +interface IGateKeeperService { + /** + * Enrolls a password, returning the handle to the enrollment to be stored locally. + * @param uid The Android user ID associated to this enrollment + * @param currentPasswordHandle The previously enrolled handle, or null if none + * @param currentPassword The previously enrolled plaintext password, or null if none. + * If provided, must verify against the currentPasswordHandle. + * @param desiredPassword The new desired password, for which a handle will be returned + * upon success. + * @return the handle corresponding to desiredPassword, or null + */ + byte[] enroll(int uid, in byte[] currentPasswordHandle, in byte[] currentPassword, + in byte[] desiredPassword); + + /** + * Verifies an enrolled handle against a provided, plaintext blob. + * @param uid The Android user ID associated to this enrollment + * @param enrolledPasswordHandle The handle against which the provided password will be + * verified. + * @param The plaintext blob to verify against enrolledPassword. + * @return True if the authentication was successful + */ + boolean verify(int uid, in byte[] enrolledPasswordHandle, + in byte[] providedPassword); + /** + * Verifies an enrolled handle against a provided, plaintext blob. + * @param uid The Android user ID associated to this enrollment + * @param challenge a challenge to authenticate agaisnt the device credential. If successful + * authentication occurs, this value will be written to the returned + * authentication attestation. + * @param enrolledPasswordHandle The handle against which the provided password will be + * verified. + * @param The plaintext blob to verify against enrolledPassword. + * @return an opaque attestation of authentication on success, or null. + */ + byte[] verifyChallenge(int uid, long challenge, in byte[] enrolledPasswordHandle, + in byte[] providedPassword); +} diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 3fa8c81..6b28746 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -1795,6 +1795,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * 11 PFLAG2_TEXT_DIRECTION_FLAGS[3] * 1 PFLAG2_TEXT_DIRECTION_FLAGS[4] * 1 1 PFLAG2_TEXT_DIRECTION_FLAGS[5] + * 11 PFLAG2_TEXT_DIRECTION_FLAGS[6] + * 111 PFLAG2_TEXT_DIRECTION_FLAGS[7] * 111 PFLAG2_TEXT_DIRECTION_MASK * 1 PFLAG2_TEXT_DIRECTION_RESOLVED * 1 PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT @@ -1968,6 +1970,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public static final int TEXT_DIRECTION_LOCALE = 5; /** + * Text direction is using "first strong algorithm". The first strong directional character + * determines the paragraph direction. If there is no strong directional character, the + * paragraph direction is LTR. + */ + public static final int TEXT_DIRECTION_FIRST_STRONG_LTR = 6; + + /** + * Text direction is using "first strong algorithm". The first strong directional character + * determines the paragraph direction. If there is no strong directional character, the + * paragraph direction is RTL. + */ + public static final int TEXT_DIRECTION_FIRST_STRONG_RTL = 7; + + /** * Default text direction is inherited */ private static final int TEXT_DIRECTION_DEFAULT = TEXT_DIRECTION_INHERIT; @@ -2002,7 +2018,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, TEXT_DIRECTION_ANY_RTL << PFLAG2_TEXT_DIRECTION_MASK_SHIFT, TEXT_DIRECTION_LTR << PFLAG2_TEXT_DIRECTION_MASK_SHIFT, TEXT_DIRECTION_RTL << PFLAG2_TEXT_DIRECTION_MASK_SHIFT, - TEXT_DIRECTION_LOCALE << PFLAG2_TEXT_DIRECTION_MASK_SHIFT + TEXT_DIRECTION_LOCALE << PFLAG2_TEXT_DIRECTION_MASK_SHIFT, + TEXT_DIRECTION_FIRST_STRONG_LTR << PFLAG2_TEXT_DIRECTION_MASK_SHIFT, + TEXT_DIRECTION_FIRST_STRONG_RTL << PFLAG2_TEXT_DIRECTION_MASK_SHIFT }; /** @@ -19636,11 +19654,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @return the defined text direction. It can be one of: * * {@link #TEXT_DIRECTION_INHERIT}, - * {@link #TEXT_DIRECTION_FIRST_STRONG} + * {@link #TEXT_DIRECTION_FIRST_STRONG}, * {@link #TEXT_DIRECTION_ANY_RTL}, * {@link #TEXT_DIRECTION_LTR}, * {@link #TEXT_DIRECTION_RTL}, - * {@link #TEXT_DIRECTION_LOCALE} + * {@link #TEXT_DIRECTION_LOCALE}, + * {@link #TEXT_DIRECTION_FIRST_STRONG_LTR}, + * {@link #TEXT_DIRECTION_FIRST_STRONG_RTL} * * @attr ref android.R.styleable#View_textDirection * @@ -19652,7 +19672,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @ViewDebug.IntToString(from = TEXT_DIRECTION_ANY_RTL, to = "ANY_RTL"), @ViewDebug.IntToString(from = TEXT_DIRECTION_LTR, to = "LTR"), @ViewDebug.IntToString(from = TEXT_DIRECTION_RTL, to = "RTL"), - @ViewDebug.IntToString(from = TEXT_DIRECTION_LOCALE, to = "LOCALE") + @ViewDebug.IntToString(from = TEXT_DIRECTION_LOCALE, to = "LOCALE"), + @ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG_LTR, to = "FIRST_STRONG_LTR"), + @ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG_RTL, to = "FIRST_STRONG_RTL") }) public int getRawTextDirection() { return (mPrivateFlags2 & PFLAG2_TEXT_DIRECTION_MASK) >> PFLAG2_TEXT_DIRECTION_MASK_SHIFT; @@ -19664,11 +19686,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param textDirection the direction to set. Should be one of: * * {@link #TEXT_DIRECTION_INHERIT}, - * {@link #TEXT_DIRECTION_FIRST_STRONG} + * {@link #TEXT_DIRECTION_FIRST_STRONG}, * {@link #TEXT_DIRECTION_ANY_RTL}, * {@link #TEXT_DIRECTION_LTR}, * {@link #TEXT_DIRECTION_RTL}, * {@link #TEXT_DIRECTION_LOCALE} + * {@link #TEXT_DIRECTION_FIRST_STRONG_LTR}, + * {@link #TEXT_DIRECTION_FIRST_STRONG_RTL}, * * Resolution will be done if the value is set to TEXT_DIRECTION_INHERIT. The resolution * proceeds up the parent chain of the view to get the value. If there is no parent, then it will @@ -19698,11 +19722,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @return the resolved text direction. Returns one of: * - * {@link #TEXT_DIRECTION_FIRST_STRONG} + * {@link #TEXT_DIRECTION_FIRST_STRONG}, * {@link #TEXT_DIRECTION_ANY_RTL}, * {@link #TEXT_DIRECTION_LTR}, * {@link #TEXT_DIRECTION_RTL}, - * {@link #TEXT_DIRECTION_LOCALE} + * {@link #TEXT_DIRECTION_LOCALE}, + * {@link #TEXT_DIRECTION_FIRST_STRONG_LTR}, + * {@link #TEXT_DIRECTION_FIRST_STRONG_RTL} * * @attr ref android.R.styleable#View_textDirection */ @@ -19712,7 +19738,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @ViewDebug.IntToString(from = TEXT_DIRECTION_ANY_RTL, to = "ANY_RTL"), @ViewDebug.IntToString(from = TEXT_DIRECTION_LTR, to = "LTR"), @ViewDebug.IntToString(from = TEXT_DIRECTION_RTL, to = "RTL"), - @ViewDebug.IntToString(from = TEXT_DIRECTION_LOCALE, to = "LOCALE") + @ViewDebug.IntToString(from = TEXT_DIRECTION_LOCALE, to = "LOCALE"), + @ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG_LTR, to = "FIRST_STRONG_LTR"), + @ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG_RTL, to = "FIRST_STRONG_RTL") }) public int getTextDirection() { return (mPrivateFlags2 & PFLAG2_TEXT_DIRECTION_RESOLVED_MASK) >> PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT; @@ -19771,6 +19799,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, case TEXT_DIRECTION_LTR: case TEXT_DIRECTION_RTL: case TEXT_DIRECTION_LOCALE: + case TEXT_DIRECTION_FIRST_STRONG_LTR: + case TEXT_DIRECTION_FIRST_STRONG_RTL: mPrivateFlags2 |= (parentResolvedDirection << PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT); break; @@ -19784,6 +19814,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, case TEXT_DIRECTION_LTR: case TEXT_DIRECTION_RTL: case TEXT_DIRECTION_LOCALE: + case TEXT_DIRECTION_FIRST_STRONG_LTR: + case TEXT_DIRECTION_FIRST_STRONG_RTL: // Resolved direction is the same as text direction mPrivateFlags2 |= (textDirection << PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT); break; diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java index f951dc2..e0b0e1f 100644 --- a/core/java/android/widget/ActionMenuPresenter.java +++ b/core/java/android/widget/ActionMenuPresenter.java @@ -40,7 +40,6 @@ import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.ListPopupWindow.ForwardingListener; -import com.android.internal.transition.ActionBarTransition; import com.android.internal.view.ActionBarPolicy; import com.android.internal.view.menu.ActionMenuItemView; import com.android.internal.view.menu.BaseMenuPresenter; @@ -99,7 +98,30 @@ public class ActionMenuPresenter extends BaseMenuPresenter // The list of currently running animations on menu items. private List<ItemAnimationInfo> mRunningItemAnimations = new ArrayList<ItemAnimationInfo>(); + private ViewTreeObserver.OnPreDrawListener mItemAnimationPreDrawListener = + new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + computeMenuItemAnimationInfo(false); + ((View) mMenuView).getViewTreeObserver().removeOnPreDrawListener(this); + runItemAnimations(); + return true; + } + }; + private View.OnAttachStateChangeListener mAttachStateChangeListener = + new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + } + @Override + public void onViewDetachedFromWindow(View v) { + ((View) mMenuView).getViewTreeObserver().removeOnPreDrawListener( + mItemAnimationPreDrawListener); + mPreLayoutItems.clear(); + mPostLayoutItems.clear(); + } + }; public ActionMenuPresenter(Context context) { @@ -177,8 +199,15 @@ public class ActionMenuPresenter extends BaseMenuPresenter @Override public MenuView getMenuView(ViewGroup root) { + MenuView oldMenuView = mMenuView; MenuView result = super.getMenuView(root); - ((ActionMenuView) result).setPresenter(this); + if (oldMenuView != result) { + ((ActionMenuView) result).setPresenter(this); + if (oldMenuView != null) { + ((View) oldMenuView).removeOnAttachStateChangeListener(mAttachStateChangeListener); + } + ((View) result).addOnAttachStateChangeListener(mAttachStateChangeListener); + } return result; } @@ -226,11 +255,11 @@ public class ActionMenuPresenter extends BaseMenuPresenter * into the MenuItemLayoutInfo structure to store the appropriate position values. */ private void computeMenuItemAnimationInfo(boolean preLayout) { - final ViewGroup menuViewParent = (ViewGroup) mMenuView; - final int count = menuViewParent.getChildCount(); + final ViewGroup menuView = (ViewGroup) mMenuView; + final int count = menuView.getChildCount(); SparseArray items = preLayout ? mPreLayoutItems : mPostLayoutItems; for (int i = 0; i < count; ++i) { - View child = menuViewParent.getChildAt(i); + View child = menuView.getChildAt(i); final int id = child.getId(); if (id > 0 && child.getWidth() != 0 && child.getHeight() != 0) { MenuItemLayoutInfo info = new MenuItemLayoutInfo(child, preLayout); @@ -377,28 +406,16 @@ public class ActionMenuPresenter extends BaseMenuPresenter * which is then fed into runItemAnimations() */ private void setupItemAnimations() { - final ViewGroup menuViewParent = (ViewGroup) mMenuView; computeMenuItemAnimationInfo(true); - final ViewTreeObserver observer = menuViewParent.getViewTreeObserver(); - if (observer != null) { - observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - computeMenuItemAnimationInfo(false); - observer.removeOnPreDrawListener(this); - runItemAnimations(); - return true; - } - }); - } + ((View) mMenuView).getViewTreeObserver(). + addOnPreDrawListener(mItemAnimationPreDrawListener); } @Override public void updateMenuView(boolean cleared) { final ViewGroup menuViewParent = (ViewGroup) ((View) mMenuView).getParent(); if (menuViewParent != null) { -// setupItemAnimations(); - ActionBarTransition.beginDelayedTransition(menuViewParent); + setupItemAnimations(); } super.updateMenuView(cleared); @@ -736,8 +753,14 @@ public class ActionMenuPresenter extends BaseMenuPresenter } public void setMenuView(ActionMenuView menuView) { - mMenuView = menuView; - menuView.initialize(mMenu); + if (menuView != mMenuView) { + if (mMenuView != null) { + ((View) mMenuView).removeOnAttachStateChangeListener(mAttachStateChangeListener); + } + mMenuView = menuView; + menuView.initialize(mMenu); + menuView.addOnAttachStateChangeListener(mAttachStateChangeListener); + } } public void setOverflowTintList(ColorStateList tint) { diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 29073be..1be05f3 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -3370,7 +3370,7 @@ public class Editor { // Parent's (TextView) previous position in window private int mLastParentX, mLastParentY; // Previous text character offset - private int mPreviousOffset = -1; + protected int mPreviousOffset = -1; // Previous text character offset private boolean mPositionHasChanged = true; // Minimum touch target size for handles @@ -3830,8 +3830,6 @@ public class Editor { } private class SelectionStartHandleView extends HandleView { - // The previous offset this handle was at. - private int mPrevOffset; // Indicates whether the cursor is making adjustments within a word. private boolean mInWord = false; // Offset to track difference between touch and word boundary. @@ -3879,7 +3877,7 @@ public class Editor { int end = getWordEnd(offset, true); int start = getWordStart(offset); - if (offset < mPrevOffset) { + if (offset < mPreviousOffset) { // User is increasing the selection. if (!mInWord || currLine < mPrevLine) { // We're not in a word, or we're on a different line so we'll expand by @@ -3888,21 +3886,19 @@ public class Editor { if (offset <= end - offsetToWord || currLine < mPrevLine) { offset = start; } else { - offset = mPrevOffset; + offset = mPreviousOffset; } } - mPrevOffset = offset; mTouchWordOffset = Math.max(trueOffset - offset, 0); mInWord = !isStartBoundary(offset); positionCursor = true; - } else if (offset - mTouchWordOffset > mPrevOffset) { + } else if (offset - mTouchWordOffset > mPreviousOffset) { // User is shrinking the selection. if (currLine > mPrevLine) { // We're on a different line, so we'll snap to word boundaries. offset = end; } offset -= mTouchWordOffset; - mPrevOffset = offset; mInWord = !isEndBoundary(offset); positionCursor = true; } @@ -3936,8 +3932,6 @@ public class Editor { } private class SelectionEndHandleView extends HandleView { - // The previous offset this handle was at. - private int mPrevOffset; // Indicates whether the cursor is making adjustments within a word. private boolean mInWord = false; // Offset to track difference between touch and word boundary. @@ -3986,7 +3980,7 @@ public class Editor { int end = getWordEnd(offset, true); int start = getWordStart(offset); - if (offset > mPrevOffset) { + if (offset > mPreviousOffset) { // User is increasing the selection. if (!mInWord || currLine > mPrevLine) { // We're not in a word, or we're on a different line so we'll expand by @@ -3995,21 +3989,19 @@ public class Editor { if (offset >= start + midPoint || currLine > mPrevLine) { offset = end; } else { - offset = mPrevOffset; + offset = mPreviousOffset; } } - mPrevOffset = offset; mTouchWordOffset = Math.max(offset - trueOffset, 0); mInWord = !isEndBoundary(offset); positionCursor = true; - } else if (offset + mTouchWordOffset < mPrevOffset) { + } else if (offset + mTouchWordOffset < mPreviousOffset) { // User is shrinking the selection. if (currLine > mPrevLine) { // We're on a different line, so we'll snap to word boundaries. offset = getWordStart(offset); } offset += mTouchWordOffset; - mPrevOffset = offset; positionCursor = true; mInWord = !isStartBoundary(offset); } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 9bbf375..b44a886 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -9275,6 +9275,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return TextDirectionHeuristics.RTL; case TEXT_DIRECTION_LOCALE: return TextDirectionHeuristics.LOCALE; + case TEXT_DIRECTION_FIRST_STRONG_LTR: + return TextDirectionHeuristics.FIRSTSTRONG_LTR; + case TEXT_DIRECTION_FIRST_STRONG_RTL: + return TextDirectionHeuristics.FIRSTSTRONG_RTL; } } |
