diff options
author | Justin Klaassen <justinklaassen@google.com> | 2018-04-03 23:21:57 -0400 |
---|---|---|
committer | Justin Klaassen <justinklaassen@google.com> | 2018-04-03 23:21:57 -0400 |
commit | 4d01eeaffaa720e4458a118baa137a11614f00f7 (patch) | |
tree | 66751893566986236788e3c796a7cc5e90d05f52 /android/hardware | |
parent | a192cc2a132cb0ee8588e2df755563ec7008c179 (diff) | |
download | android-28-4d01eeaffaa720e4458a118baa137a11614f00f7.tar.gz |
Import Android SDK Platform P [4697573]
/google/data/ro/projects/android/fetch_artifact \
--bid 4697573 \
--target sdk_phone_armv7-win_sdk \
sdk-repo-linux-sources-4697573.zip
AndroidVersion.ApiLevel has been modified to appear as 28
Change-Id: If80578c3c657366cc9cf75f8db13d46e2dd4e077
Diffstat (limited to 'android/hardware')
72 files changed, 4102 insertions, 1941 deletions
diff --git a/android/hardware/Camera.java b/android/hardware/Camera.java index 931b5c91..9a276fbd 100644 --- a/android/hardware/Camera.java +++ b/android/hardware/Camera.java @@ -242,6 +242,9 @@ public class Camera { /** * Returns the number of physical cameras available on this device. + * The return value of this method might change dynamically if the device + * supports external cameras and an external camera is connected or + * disconnected. * * @return total number of accessible camera devices, or 0 if there are no * cameras or an error was encountered enumerating them. @@ -3532,8 +3535,8 @@ public class Camera { /** * Gets the focal length (in millimeter) of the camera. * - * @return the focal length. This method will always return a valid - * value. + * @return the focal length. Returns -1.0 when the device + * doesn't report focal length information. */ public float getFocalLength() { return Float.parseFloat(get(KEY_FOCAL_LENGTH)); @@ -3542,8 +3545,8 @@ public class Camera { /** * Gets the horizontal angle of view in degrees. * - * @return horizontal angle of view. This method will always return a - * valid value. + * @return horizontal angle of view. Returns -1.0 when the device + * doesn't report view angle information. */ public float getHorizontalViewAngle() { return Float.parseFloat(get(KEY_HORIZONTAL_VIEW_ANGLE)); @@ -3552,8 +3555,8 @@ public class Camera { /** * Gets the vertical angle of view in degrees. * - * @return vertical angle of view. This method will always return a - * valid value. + * @return vertical angle of view. Returns -1.0 when the device + * doesn't report view angle information. */ public float getVerticalViewAngle() { return Float.parseFloat(get(KEY_VERTICAL_VIEW_ANGLE)); diff --git a/android/hardware/ConsumerIrManager.java b/android/hardware/ConsumerIrManager.java index c7a33ffa..6f589cd9 100644 --- a/android/hardware/ConsumerIrManager.java +++ b/android/hardware/ConsumerIrManager.java @@ -16,8 +16,10 @@ package android.hardware; +import android.annotation.RequiresFeature; import android.annotation.SystemService; import android.content.Context; +import android.content.pm.PackageManager; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; @@ -27,6 +29,7 @@ import android.util.Log; * Class that operates consumer infrared on the device. */ @SystemService(Context.CONSUMER_IR_SERVICE) +@RequiresFeature(PackageManager.FEATURE_CONSUMER_IR) public final class ConsumerIrManager { private static final String TAG = "ConsumerIr"; diff --git a/android/hardware/Sensor.java b/android/hardware/Sensor.java index 7fb0c89e..72974261 100644 --- a/android/hardware/Sensor.java +++ b/android/hardware/Sensor.java @@ -22,7 +22,9 @@ import android.os.Build; /** * Class representing a sensor. Use {@link SensorManager#getSensorList} to get - * the list of available Sensors. + * the list of available sensors. For more information about Android sensors, + * read the + * <a href="/guide/topics/sensors/sensors_motion.html">Motion Sensors guide</a>.</p> * * @see SensorManager * @see SensorEventListener diff --git a/android/hardware/biometrics/BiometricAuthenticator.java b/android/hardware/biometrics/BiometricAuthenticator.java new file mode 100644 index 00000000..c811999c --- /dev/null +++ b/android/hardware/biometrics/BiometricAuthenticator.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2018 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.biometrics; + +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; +import android.os.CancellationSignal; +import android.os.Parcelable; + +import java.util.concurrent.Executor; + +/** + * This is the common interface that all biometric authentication classes should implement. + * @hide + */ +public interface BiometricAuthenticator { + + /** + * Container for biometric data + * @hide + */ + abstract class BiometricIdentifier implements Parcelable {} + + /** + * Container for callback data from {@link BiometricAuthenticator#authenticate( + * CancellationSignal, Executor, AuthenticationCallback)} and + * {@link BiometricAuthenticator#authenticate(CryptoObject, CancellationSignal, Executor, + * AuthenticationCallback)} + */ + class AuthenticationResult { + private BiometricIdentifier mIdentifier; + private CryptoObject mCryptoObject; + private int mUserId; + + /** + * @hide + */ + public AuthenticationResult() { } + + /** + * Authentication result + * @param crypto + * @param identifier + * @param userId + * @hide + */ + public AuthenticationResult(CryptoObject crypto, BiometricIdentifier identifier, + int userId) { + mCryptoObject = crypto; + mIdentifier = identifier; + mUserId = userId; + } + + /** + * Obtain the crypto object associated with this transaction + * @return crypto object provided to {@link BiometricAuthenticator#authenticate( + * CryptoObject, CancellationSignal, Executor, AuthenticationCallback)} + */ + public CryptoObject getCryptoObject() { + return mCryptoObject; + } + + /** + * Obtain the biometric identifier associated with this operation. Applications are strongly + * discouraged from associating specific identifiers with specific applications or + * operations. + * @hide + */ + public BiometricIdentifier getId() { + return mIdentifier; + } + + /** + * Obtain the userId for which this biometric was authenticated. + * @hide + */ + public int getUserId() { + return mUserId; + } + }; + + /** + * Callback structure provided to {@link BiometricAuthenticator#authenticate(CancellationSignal, + * Executor, AuthenticationCallback)} or {@link BiometricAuthenticator#authenticate( + * CryptoObject, CancellationSignal, Executor, AuthenticationCallback)}. Users must provide + * an implementation of this for listening to biometric events. + */ + abstract class AuthenticationCallback { + /** + * Called when an unrecoverable error has been encountered and the operation is complete. + * No further actions will be made on this object. + * @param errorCode An integer identifying the error message + * @param errString A human-readable error string that can be shown on an UI + */ + public void onAuthenticationError(int errorCode, CharSequence errString) {} + + /** + * Called when a recoverable error has been encountered during authentication. The help + * string is provided to give the user guidance for what went wrong, such as "Sensor dirty, + * please clean it." + * @param helpCode An integer identifying the error message + * @param helpString A human-readable string that can be shown on an UI + */ + public void onAuthenticationHelp(int helpCode, CharSequence helpString) {} + + /** + * Called when a biometric is recognized. + * @param result An object containing authentication-related data + */ + public void onAuthenticationSucceeded(AuthenticationResult result) {} + + /** + * Called when a biometric is valid but not recognized. + */ + public void onAuthenticationFailed() {} + + /** + * Called when a biometric has been acquired, but hasn't been processed yet. + * @hide + */ + public void onAuthenticationAcquired(int acquireInfo) {} + }; + + /** + * This call warms up the hardware and starts scanning for valid biometrics. It terminates + * when {@link AuthenticationCallback#onAuthenticationError(int, + * CharSequence)} is called or when {@link AuthenticationCallback#onAuthenticationSucceeded( + * AuthenticationResult)} is called, at which point the crypto object becomes invalid. This + * operation can be canceled by using the provided cancel object. The application wil receive + * authentication errors through {@link AuthenticationCallback}. Calling + * {@link BiometricAuthenticator#authenticate(CryptoObject, CancellationSignal, Executor, + * AuthenticationCallback)} while an existing authentication attempt is occurring will stop + * the previous client and start a new authentication. The interrupted client will receive a + * cancelled notification through {@link AuthenticationCallback#onAuthenticationError(int, + * CharSequence)}. + * + * @throws IllegalArgumentException If any of the arguments are null + * + * @param crypto Object associated with the call + * @param cancel An object that can be used to cancel authentication + * @param executor An executor to handle callback events + * @param callback An object to receive authentication events + */ + void authenticate(@NonNull CryptoObject crypto, + @NonNull CancellationSignal cancel, + @NonNull @CallbackExecutor Executor executor, + @NonNull AuthenticationCallback callback); + + /** + * This call warms up the hardware and starts scanning for valid biometrics. It terminates + * when {@link AuthenticationCallback#onAuthenticationError(int, + * CharSequence)} is called or when {@link AuthenticationCallback#onAuthenticationSucceeded( + * AuthenticationResult)} is called. This operation can be canceled by using the provided cancel + * object. The application wil receive authentication errors through + * {@link AuthenticationCallback}. Calling {@link BiometricAuthenticator#authenticate( + * CryptoObject, CancellationSignal, Executor, AuthenticationCallback)} while an existing + * authentication attempt is occurring will stop the previous client and start a new + * authentication. The interrupted client will receive a cancelled notification through + * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)}. + * + * @throws IllegalArgumentException If any of the arguments are null + * + * @param cancel An object that can be used to cancel authentication + * @param executor An executor to handle callback events + * @param callback An object to receive authentication events + */ + void authenticate(@NonNull CancellationSignal cancel, + @NonNull @CallbackExecutor Executor executor, + @NonNull AuthenticationCallback callback); +} diff --git a/android/hardware/biometrics/BiometricConstants.java b/android/hardware/biometrics/BiometricConstants.java new file mode 100644 index 00000000..a037289a --- /dev/null +++ b/android/hardware/biometrics/BiometricConstants.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2018 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.biometrics; + + +/** + * Interface containing all of the biometric modality agnostic constants. + * @hide + */ +public interface BiometricConstants { + // + // Error messages from biometric hardware during initilization, enrollment, authentication or + // removal. + // + + /** + * The hardware is unavailable. Try again later. + */ + int BIOMETRIC_ERROR_HW_UNAVAILABLE = 1; + + /** + * Error state returned when the sensor was unable to process the current image. + */ + int BIOMETRIC_ERROR_UNABLE_TO_PROCESS = 2; + + /** + * Error state returned when the current request has been running too long. This is intended to + * prevent programs from waiting for the biometric sensor indefinitely. The timeout is platform + * and sensor-specific, but is generally on the order of 30 seconds. + */ + int BIOMETRIC_ERROR_TIMEOUT = 3; + + /** + * Error state returned for operations like enrollment; the operation cannot be completed + * because there's not enough storage remaining to complete the operation. + */ + int BIOMETRIC_ERROR_NO_SPACE = 4; + + /** + * The operation was canceled because the biometric sensor is unavailable. For example, this may + * happen when the user is switched, the device is locked or another pending operation prevents + * or disables it. + */ + int BIOMETRIC_ERROR_CANCELED = 5; + + /** + * The {@link BiometricManager#remove} call failed. Typically this will happen when the provided + * biometric id was incorrect. + * + * @hide + */ + int BIOMETRIC_ERROR_UNABLE_TO_REMOVE = 6; + + /** + * The operation was canceled because the API is locked out due to too many attempts. + * This occurs after 5 failed attempts, and lasts for 30 seconds. + */ + int BIOMETRIC_ERROR_LOCKOUT = 7; + + /** + * Hardware vendors may extend this list if there are conditions that do not fall under one of + * the above categories. Vendors are responsible for providing error strings for these errors. + * These messages are typically reserved for internal operations such as enrollment, but may be + * used to express vendor errors not otherwise covered. Applications are expected to show the + * error message string if they happen, but are advised not to rely on the message id since they + * will be device and vendor-specific + */ + int BIOMETRIC_ERROR_VENDOR = 8; + + /** + * The operation was canceled because BIOMETRIC_ERROR_LOCKOUT occurred too many times. + * Biometric authentication is disabled until the user unlocks with strong authentication + * (PIN/Pattern/Password) + */ + int BIOMETRIC_ERROR_LOCKOUT_PERMANENT = 9; + + /** + * The user canceled the operation. Upon receiving this, applications should use alternate + * authentication (e.g. a password). The application should also provide the means to return to + * biometric authentication, such as a "use <biometric>" button. + */ + int BIOMETRIC_ERROR_USER_CANCELED = 10; + + /** + * The user does not have any biometrics enrolled. + */ + int BIOMETRIC_ERROR_NO_BIOMETRICS = 11; + + /** + * The device does not have a biometric sensor. + */ + int BIOMETRIC_ERROR_HW_NOT_PRESENT = 12; + + /** + * @hide + */ + int BIOMETRIC_ERROR_VENDOR_BASE = 1000; + + // + // Image acquisition messages. + // + + /** + * The image acquired was good. + */ + int BIOMETRIC_ACQUIRED_GOOD = 0; + + /** + * Only a partial biometric image was detected. During enrollment, the user should be informed + * on what needs to happen to resolve this problem, e.g. "press firmly on sensor." (for + * fingerprint) + */ + int BIOMETRIC_ACQUIRED_PARTIAL = 1; + + /** + * The biometric image was too noisy to process due to a detected condition or a possibly dirty + * sensor (See {@link #BIOMETRIC_ACQUIRED_IMAGER_DIRTY}). + */ + int BIOMETRIC_ACQUIRED_INSUFFICIENT = 2; + + /** + * The biometric image was too noisy due to suspected or detected dirt on the sensor. For + * example, it's reasonable return this after multiple {@link #BIOMETRIC_ACQUIRED_INSUFFICIENT} + * or actual detection of dirt on the sensor (stuck pixels, swaths, etc.). The user is expected + * to take action to clean the sensor when this is returned. + */ + int BIOMETRIC_ACQUIRED_IMAGER_DIRTY = 3; + + /** + * The biometric image was unreadable due to lack of motion. + */ + int BIOMETRIC_ACQUIRED_TOO_SLOW = 4; + + /** + * The biometric image was incomplete due to quick motion. For example, this could also happen + * if the user moved during acquisition. The user should be asked to repeat the operation more + * slowly. + */ + int BIOMETRIC_ACQUIRED_TOO_FAST = 5; + + /** + * Hardware vendors may extend this list if there are conditions that do not fall under one of + * the above categories. Vendors are responsible for providing error strings for these errors. + * @hide + */ + int BIOMETRIC_ACQUIRED_VENDOR = 6; + /** + * @hide + */ + int BIOMETRICT_ACQUIRED_VENDOR_BASE = 1000; +} diff --git a/android/hardware/biometrics/BiometricDialog.java b/android/hardware/biometrics/BiometricDialog.java new file mode 100644 index 00000000..dd848a34 --- /dev/null +++ b/android/hardware/biometrics/BiometricDialog.java @@ -0,0 +1,494 @@ +/* + * Copyright (C) 2018 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.biometrics; + +import static android.Manifest.permission.USE_BIOMETRIC; + +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.content.Context; +import android.content.DialogInterface; +import android.content.pm.PackageManager; +import android.hardware.fingerprint.FingerprintManager; +import android.os.Bundle; +import android.os.CancellationSignal; +import android.text.TextUtils; + +import java.security.Signature; +import java.util.concurrent.Executor; + +import javax.crypto.Cipher; +import javax.crypto.Mac; + +/** + * A class that manages a system-provided biometric dialog. + */ +public class BiometricDialog implements BiometricAuthenticator, BiometricConstants { + + /** + * @hide + */ + public static final String KEY_TITLE = "title"; + /** + * @hide + */ + public static final String KEY_SUBTITLE = "subtitle"; + /** + * @hide + */ + public static final String KEY_DESCRIPTION = "description"; + /** + * @hide + */ + public static final String KEY_POSITIVE_TEXT = "positive_text"; + /** + * @hide + */ + public static final String KEY_NEGATIVE_TEXT = "negative_text"; + + /** + * Error/help message will show for this amount of time. + * For error messages, the dialog will also be dismissed after this amount of time. + * Error messages will be propagated back to the application via AuthenticationCallback + * after this amount of time. + * @hide + */ + public static final int HIDE_DIALOG_DELAY = 2000; // ms + /** + * @hide + */ + public static final int DISMISSED_REASON_POSITIVE = 1; + + /** + * @hide + */ + public static final int DISMISSED_REASON_NEGATIVE = 2; + + /** + * @hide + */ + public static final int DISMISSED_REASON_USER_CANCEL = 3; + + private static class ButtonInfo { + Executor executor; + DialogInterface.OnClickListener listener; + ButtonInfo(Executor ex, DialogInterface.OnClickListener l) { + executor = ex; + listener = l; + } + } + + /** + * A builder that collects arguments to be shown on the system-provided biometric dialog. + **/ + public static class Builder { + private final Bundle mBundle; + private ButtonInfo mPositiveButtonInfo; + private ButtonInfo mNegativeButtonInfo; + private Context mContext; + + /** + * Creates a builder for a biometric dialog. + * @param context + */ + public Builder(Context context) { + mBundle = new Bundle(); + mContext = context; + } + + /** + * Required: Set the title to display. + * @param title + * @return + */ + public Builder setTitle(@NonNull CharSequence title) { + mBundle.putCharSequence(KEY_TITLE, title); + return this; + } + + /** + * Optional: Set the subtitle to display. + * @param subtitle + * @return + */ + public Builder setSubtitle(@NonNull CharSequence subtitle) { + mBundle.putCharSequence(KEY_SUBTITLE, subtitle); + return this; + } + + /** + * Optional: Set the description to display. + * @param description + * @return + */ + public Builder setDescription(@NonNull CharSequence description) { + mBundle.putCharSequence(KEY_DESCRIPTION, description); + return this; + } + + /** + * Optional: Set the text for the positive button. If not set, the positive button + * will not show. + * @param text + * @return + * @hide + */ + public Builder setPositiveButton(@NonNull CharSequence text, + @NonNull @CallbackExecutor Executor executor, + @NonNull DialogInterface.OnClickListener listener) { + if (TextUtils.isEmpty(text)) { + throw new IllegalArgumentException("Text must be set and non-empty"); + } + if (executor == null) { + throw new IllegalArgumentException("Executor must not be null"); + } + if (listener == null) { + throw new IllegalArgumentException("Listener must not be null"); + } + mBundle.putCharSequence(KEY_POSITIVE_TEXT, text); + mPositiveButtonInfo = new ButtonInfo(executor, listener); + return this; + } + + /** + * Required: Set the text for the negative button. This would typically be used as a + * "Cancel" button, but may be also used to show an alternative method for authentication, + * such as screen that asks for a backup password. + * @param text + * @return + */ + public Builder setNegativeButton(@NonNull CharSequence text, + @NonNull @CallbackExecutor Executor executor, + @NonNull DialogInterface.OnClickListener listener) { + if (TextUtils.isEmpty(text)) { + throw new IllegalArgumentException("Text must be set and non-empty"); + } + if (executor == null) { + throw new IllegalArgumentException("Executor must not be null"); + } + if (listener == null) { + throw new IllegalArgumentException("Listener must not be null"); + } + mBundle.putCharSequence(KEY_NEGATIVE_TEXT, text); + mNegativeButtonInfo = new ButtonInfo(executor, listener); + return this; + } + + /** + * Creates a {@link BiometricDialog}. + * @return a {@link BiometricDialog} + * @throws IllegalArgumentException if any of the required fields are not set. + */ + public BiometricDialog build() { + final CharSequence title = mBundle.getCharSequence(KEY_TITLE); + final CharSequence negative = mBundle.getCharSequence(KEY_NEGATIVE_TEXT); + + if (TextUtils.isEmpty(title)) { + throw new IllegalArgumentException("Title must be set and non-empty"); + } else if (TextUtils.isEmpty(negative)) { + throw new IllegalArgumentException("Negative text must be set and non-empty"); + } + return new BiometricDialog(mContext, mBundle, mPositiveButtonInfo, mNegativeButtonInfo); + } + } + + private PackageManager mPackageManager; + private FingerprintManager mFingerprintManager; + private Bundle mBundle; + private ButtonInfo mPositiveButtonInfo; + private ButtonInfo mNegativeButtonInfo; + + IBiometricDialogReceiver mDialogReceiver = new IBiometricDialogReceiver.Stub() { + @Override + public void onDialogDismissed(int reason) { + // Check the reason and invoke OnClickListener(s) if necessary + if (reason == DISMISSED_REASON_POSITIVE) { + mPositiveButtonInfo.executor.execute(() -> { + mPositiveButtonInfo.listener.onClick(null, DialogInterface.BUTTON_POSITIVE); + }); + } else if (reason == DISMISSED_REASON_NEGATIVE) { + mNegativeButtonInfo.executor.execute(() -> { + mNegativeButtonInfo.listener.onClick(null, DialogInterface.BUTTON_NEGATIVE); + }); + } + } + }; + + private BiometricDialog(Context context, Bundle bundle, + ButtonInfo positiveButtonInfo, ButtonInfo negativeButtonInfo) { + mBundle = bundle; + mPositiveButtonInfo = positiveButtonInfo; + mNegativeButtonInfo = negativeButtonInfo; + mFingerprintManager = context.getSystemService(FingerprintManager.class); + mPackageManager = context.getPackageManager(); + } + + /** + * A wrapper class for the crypto objects supported by BiometricDialog. Currently the framework + * supports {@link Signature}, {@link Cipher} and {@link Mac} objects. + */ + public static final class CryptoObject extends android.hardware.biometrics.CryptoObject { + public CryptoObject(@NonNull Signature signature) { + super(signature); + } + + public CryptoObject(@NonNull Cipher cipher) { + super(cipher); + } + + public CryptoObject(@NonNull Mac mac) { + super(mac); + } + + /** + * Get {@link Signature} object. + * @return {@link Signature} object or null if this doesn't contain one. + */ + public Signature getSignature() { + return super.getSignature(); + } + + /** + * Get {@link Cipher} object. + * @return {@link Cipher} object or null if this doesn't contain one. + */ + public Cipher getCipher() { + return super.getCipher(); + } + + /** + * Get {@link Mac} object. + * @return {@link Mac} object or null if this doesn't contain one. + */ + public Mac getMac() { + return super.getMac(); + } + } + + /** + * Container for callback data from {@link #authenticate( CancellationSignal, Executor, + * AuthenticationCallback)} and {@link #authenticate(CryptoObject, CancellationSignal, Executor, + * AuthenticationCallback)} + */ + public static class AuthenticationResult extends BiometricAuthenticator.AuthenticationResult { + /** + * Authentication result + * @param crypto + * @param identifier + * @param userId + * @hide + */ + public AuthenticationResult(CryptoObject crypto, BiometricIdentifier identifier, + int userId) { + super(crypto, identifier, userId); + } + /** + * Obtain the crypto object associated with this transaction + * @return crypto object provided to {@link #authenticate( CryptoObject, CancellationSignal, + * Executor, AuthenticationCallback)} + */ + public CryptoObject getCryptoObject() { + return (CryptoObject) super.getCryptoObject(); + } + } + + /** + * Callback structure provided to {@link BiometricDialog#authenticate(CancellationSignal, + * Executor, AuthenticationCallback)} or {@link BiometricDialog#authenticate(CryptoObject, + * CancellationSignal, Executor, AuthenticationCallback)}. Users must provide an implementation + * of this for listening to authentication events. + */ + public abstract static class AuthenticationCallback extends + BiometricAuthenticator.AuthenticationCallback { + /** + * Called when an unrecoverable error has been encountered and the operation is complete. + * No further actions will be made on this object. + * @param errorCode An integer identifying the error message + * @param errString A human-readable error string that can be shown on an UI + */ + @Override + public void onAuthenticationError(int errorCode, CharSequence errString) {} + + /** + * Called when a recoverable error has been encountered during authentication. The help + * string is provided to give the user guidance for what went wrong, such as "Sensor dirty, + * please clean it." + * @param helpCode An integer identifying the error message + * @param helpString A human-readable string that can be shown on an UI + */ + @Override + public void onAuthenticationHelp(int helpCode, CharSequence helpString) {} + + /** + * Called when a biometric is recognized. + * @param result An object containing authentication-related data + */ + public void onAuthenticationSucceeded(AuthenticationResult result) {} + + /** + * Called when a biometric is valid but not recognized. + */ + @Override + public void onAuthenticationFailed() {} + + /** + * Called when a biometric has been acquired, but hasn't been processed yet. + * @hide + */ + @Override + public void onAuthenticationAcquired(int acquireInfo) {} + + /** + * @param result An object containing authentication-related data + * @hide + */ + @Override + public void onAuthenticationSucceeded(BiometricAuthenticator.AuthenticationResult result) { + onAuthenticationSucceeded(new AuthenticationResult( + (CryptoObject) result.getCryptoObject(), + result.getId(), + result.getUserId())); + } + } + + /** + * @param crypto Object associated with the call + * @param cancel An object that can be used to cancel authentication + * @param executor An executor to handle callback events + * @param callback An object to receive authentication events + * @hide + */ + @Override + public void authenticate(@NonNull android.hardware.biometrics.CryptoObject crypto, + @NonNull CancellationSignal cancel, + @NonNull @CallbackExecutor Executor executor, + @NonNull BiometricAuthenticator.AuthenticationCallback callback) { + if (!(callback instanceof BiometricDialog.AuthenticationCallback)) { + throw new IllegalArgumentException("Callback cannot be casted"); + } + authenticate(crypto, cancel, executor, (AuthenticationCallback) callback); + } + + /** + * + * @param cancel An object that can be used to cancel authentication + * @param executor An executor to handle callback events + * @param callback An object to receive authentication events + * @hide + */ + @Override + public void authenticate(@NonNull CancellationSignal cancel, + @NonNull @CallbackExecutor Executor executor, + @NonNull BiometricAuthenticator.AuthenticationCallback callback) { + if (!(callback instanceof BiometricDialog.AuthenticationCallback)) { + throw new IllegalArgumentException("Callback cannot be casted"); + } + authenticate(cancel, executor, (AuthenticationCallback) callback); + } + + /** + * This call warms up the fingerprint hardware, displays a system-provided dialog, and starts + * scanning for a fingerprint. It terminates when {@link + * AuthenticationCallback#onAuthenticationError(int, CharSequence)} is called, when {@link + * AuthenticationCallback#onAuthenticationSucceeded( AuthenticationResult)}, or when the user + * dismisses the system-provided dialog, at which point the crypto object becomes invalid. This + * operation can be canceled by using the provided cancel object. The application will receive + * authentication errors through {@link AuthenticationCallback}, and button events through the + * corresponding callback set in {@link Builder#setNegativeButton(CharSequence, Executor, + * DialogInterface.OnClickListener)}. It is safe to reuse the {@link BiometricDialog} object, + * and calling {@link BiometricDialog#authenticate( CancellationSignal, Executor, + * AuthenticationCallback)} while an existing authentication attempt is occurring will stop the + * previous client and start a new authentication. The interrupted client will receive a + * cancelled notification through {@link AuthenticationCallback#onAuthenticationError(int, + * CharSequence)}. + * + * @throws IllegalArgumentException If any of the arguments are null + * + * @param crypto Object associated with the call + * @param cancel An object that can be used to cancel authentication + * @param executor An executor to handle callback events + * @param callback An object to receive authentication events + */ + @RequiresPermission(USE_BIOMETRIC) + public void authenticate(@NonNull CryptoObject crypto, + @NonNull CancellationSignal cancel, + @NonNull @CallbackExecutor Executor executor, + @NonNull AuthenticationCallback callback) { + if (handlePreAuthenticationErrors(callback, executor)) { + return; + } + mFingerprintManager.authenticate(crypto, cancel, mBundle, executor, mDialogReceiver, + callback); + } + + /** + * This call warms up the fingerprint hardware, displays a system-provided dialog, and starts + * scanning for a fingerprint. It terminates when {@link + * AuthenticationCallback#onAuthenticationError(int, CharSequence)} is called, when {@link + * AuthenticationCallback#onAuthenticationSucceeded( AuthenticationResult)} is called, or when + * the user dismisses the system-provided dialog. This operation can be canceled by using the + * provided cancel object. The application will receive authentication errors through {@link + * AuthenticationCallback}, and button events through the corresponding callback set in {@link + * Builder#setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}. It is + * safe to reuse the {@link BiometricDialog} object, and calling {@link + * BiometricDialog#authenticate(CancellationSignal, Executor, AuthenticationCallback)} while + * an existing authentication attempt is occurring will stop the previous client and start a new + * authentication. The interrupted client will receive a cancelled notification through {@link + * AuthenticationCallback#onAuthenticationError(int, CharSequence)}. + * + * @throws IllegalArgumentException If any of the arguments are null + * + * @param cancel An object that can be used to cancel authentication + * @param executor An executor to handle callback events + * @param callback An object to receive authentication events + */ + @RequiresPermission(USE_BIOMETRIC) + public void authenticate(@NonNull CancellationSignal cancel, + @NonNull @CallbackExecutor Executor executor, + @NonNull AuthenticationCallback callback) { + if (handlePreAuthenticationErrors(callback, executor)) { + return; + } + mFingerprintManager.authenticate(cancel, mBundle, executor, mDialogReceiver, callback); + } + + private boolean handlePreAuthenticationErrors(AuthenticationCallback callback, + Executor executor) { + if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { + sendError(BiometricDialog.BIOMETRIC_ERROR_HW_NOT_PRESENT, callback, + executor); + return true; + } else if (!mFingerprintManager.isHardwareDetected()) { + sendError(BiometricDialog.BIOMETRIC_ERROR_HW_UNAVAILABLE, callback, + executor); + return true; + } else if (!mFingerprintManager.hasEnrolledFingerprints()) { + sendError(BiometricDialog.BIOMETRIC_ERROR_NO_BIOMETRICS, callback, + executor); + return true; + } + return false; + } + + private void sendError(int error, AuthenticationCallback callback, Executor executor) { + executor.execute(() -> { + callback.onAuthenticationError(error, mFingerprintManager.getErrorString( + error, 0 /* vendorCode */)); + }); + } +} diff --git a/android/hardware/biometrics/BiometricFingerprintConstants.java b/android/hardware/biometrics/BiometricFingerprintConstants.java new file mode 100644 index 00000000..638f525b --- /dev/null +++ b/android/hardware/biometrics/BiometricFingerprintConstants.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2018 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.biometrics; + +import android.hardware.fingerprint.FingerprintManager; + +/** + * Interface containing all of the fingerprint-specific constants. + * @hide + */ +public interface BiometricFingerprintConstants { + // + // Error messages from fingerprint hardware during initilization, enrollment, authentication or + // removal. Must agree with the list in fingerprint.h + // + + /** + * The hardware is unavailable. Try again later. + */ + public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; + + /** + * Error state returned when the sensor was unable to process the current image. + */ + public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; + + /** + * Error state returned when the current request has been running too long. This is intended to + * prevent programs from waiting for the fingerprint sensor indefinitely. The timeout is + * platform and sensor-specific, but is generally on the order of 30 seconds. + */ + public static final int FINGERPRINT_ERROR_TIMEOUT = 3; + + /** + * Error state returned for operations like enrollment; the operation cannot be completed + * because there's not enough storage remaining to complete the operation. + */ + public static final int FINGERPRINT_ERROR_NO_SPACE = 4; + + /** + * The operation was canceled because the fingerprint sensor is unavailable. For example, + * this may happen when the user is switched, the device is locked or another pending operation + * prevents or disables it. + */ + public static final int FINGERPRINT_ERROR_CANCELED = 5; + + /** + * The {@link FingerprintManager#remove} call failed. Typically this will happen when the + * provided fingerprint id was incorrect. + * + * @hide + */ + public static final int FINGERPRINT_ERROR_UNABLE_TO_REMOVE = 6; + + /** + * The operation was canceled because the API is locked out due to too many attempts. + * This occurs after 5 failed attempts, and lasts for 30 seconds. + */ + public static final int FINGERPRINT_ERROR_LOCKOUT = 7; + + /** + * Hardware vendors may extend this list if there are conditions that do not fall under one of + * the above categories. Vendors are responsible for providing error strings for these errors. + * These messages are typically reserved for internal operations such as enrollment, but may be + * used to express vendor errors not covered by the ones in fingerprint.h. Applications are + * expected to show the error message string if they happen, but are advised not to rely on the + * message id since they will be device and vendor-specific + */ + public static final int FINGERPRINT_ERROR_VENDOR = 8; + + /** + * The operation was canceled because FINGERPRINT_ERROR_LOCKOUT occurred too many times. + * Fingerprint authentication is disabled until the user unlocks with strong authentication + * (PIN/Pattern/Password) + */ + public static final int FINGERPRINT_ERROR_LOCKOUT_PERMANENT = 9; + + /** + * The user canceled the operation. Upon receiving this, applications should use alternate + * authentication (e.g. a password). The application should also provide the means to return + * to fingerprint authentication, such as a "use fingerprint" button. + */ + public static final int FINGERPRINT_ERROR_USER_CANCELED = 10; + + /** + * The user does not have any fingerprints enrolled. + */ + public static final int FINGERPRINT_ERROR_NO_FINGERPRINTS = 11; + + /** + * The device does not have a fingerprint sensor. + */ + public static final int FINGERPRINT_ERROR_HW_NOT_PRESENT = 12; + + /** + * @hide + */ + public static final int FINGERPRINT_ERROR_VENDOR_BASE = 1000; + + // + // Image acquisition messages. Must agree with those in fingerprint.h + // + + /** + * The image acquired was good. + */ + public static final int FINGERPRINT_ACQUIRED_GOOD = 0; + + /** + * Only a partial fingerprint image was detected. During enrollment, the user should be + * informed on what needs to happen to resolve this problem, e.g. "press firmly on sensor." + */ + public static final int FINGERPRINT_ACQUIRED_PARTIAL = 1; + + /** + * The fingerprint image was too noisy to process due to a detected condition (i.e. dry skin) or + * a possibly dirty sensor (See {@link #FINGERPRINT_ACQUIRED_IMAGER_DIRTY}). + */ + public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; + + /** + * The fingerprint image was too noisy due to suspected or detected dirt on the sensor. + * For example, it's reasonable return this after multiple + * {@link #FINGERPRINT_ACQUIRED_INSUFFICIENT} or actual detection of dirt on the sensor + * (stuck pixels, swaths, etc.). The user is expected to take action to clean the sensor + * when this is returned. + */ + public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3; + + /** + * The fingerprint image was unreadable due to lack of motion. This is most appropriate for + * linear array sensors that require a swipe motion. + */ + public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 4; + + /** + * The fingerprint image was incomplete due to quick motion. While mostly appropriate for + * linear array sensors, this could also happen if the finger was moved during acquisition. + * The user should be asked to move the finger slower (linear) or leave the finger on the sensor + * longer. + */ + public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 5; + + /** + * Hardware vendors may extend this list if there are conditions that do not fall under one of + * the above categories. Vendors are responsible for providing error strings for these errors. + * @hide + */ + public static final int FINGERPRINT_ACQUIRED_VENDOR = 6; + /** + * @hide + */ + public static final int FINGERPRINT_ACQUIRED_VENDOR_BASE = 1000; +} diff --git a/android/hardware/biometrics/CryptoObject.java b/android/hardware/biometrics/CryptoObject.java new file mode 100644 index 00000000..496d9c57 --- /dev/null +++ b/android/hardware/biometrics/CryptoObject.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2018 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.biometrics; + +import android.annotation.NonNull; +import android.security.keystore.AndroidKeyStoreProvider; + +import java.security.Signature; + +import javax.crypto.Cipher; +import javax.crypto.Mac; + +/** + * A wrapper class for the crypto objects supported by FingerprintManager. Currently the + * framework supports {@link Signature}, {@link Cipher} and {@link Mac} objects. + * @hide + */ +public class CryptoObject { + private final Object mCrypto; + + public CryptoObject(@NonNull Signature signature) { + mCrypto = signature; + } + + public CryptoObject(@NonNull Cipher cipher) { + mCrypto = cipher; + } + + public CryptoObject(@NonNull Mac mac) { + mCrypto = mac; + } + + /** + * Get {@link Signature} object. + * @return {@link Signature} object or null if this doesn't contain one. + */ + public Signature getSignature() { + return mCrypto instanceof Signature ? (Signature) mCrypto : null; + } + + /** + * Get {@link Cipher} object. + * @return {@link Cipher} object or null if this doesn't contain one. + */ + public Cipher getCipher() { + return mCrypto instanceof Cipher ? (Cipher) mCrypto : null; + } + + /** + * Get {@link Mac} object. + * @return {@link Mac} object or null if this doesn't contain one. + */ + public Mac getMac() { + return mCrypto instanceof Mac ? (Mac) mCrypto : null; + } + + /** + * @hide + * @return the opId associated with this object or 0 if none + */ + public final long getOpId() { + return mCrypto != null + ? AndroidKeyStoreProvider.getKeyStoreOperationHandle(mCrypto) : 0; + } +}; diff --git a/android/hardware/camera2/CameraCaptureSession.java b/android/hardware/camera2/CameraCaptureSession.java index ff69bd89..eafe5938 100644 --- a/android/hardware/camera2/CameraCaptureSession.java +++ b/android/hardware/camera2/CameraCaptureSession.java @@ -16,15 +16,16 @@ package android.hardware.camera2; +import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; import android.hardware.camera2.params.OutputConfiguration; import android.os.Handler; import android.view.Surface; +import java.util.concurrent.Executor; import java.util.List; - /** * 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. @@ -354,6 +355,50 @@ public abstract class CameraCaptureSession implements AutoCloseable { throws CameraAccessException; /** + * <p>Submit a request for an image to be captured by the camera device.</p> + * + * <p>The behavior of this method matches that of + * {@link #capture(CaptureRequest, CaptureCallback, Handler)}, + * except that it uses {@link java.util.concurrent.Executor} as an argument + * instead of {@link android.os.Handler}.</p> + * + * @param request the settings for this capture + * @param executor the executor which will be used for invoking the listener. + * @param listener The callback object to notify once this request has been + * processed. + * + * @return int A unique capture sequence ID used by + * {@link CaptureCallback#onCaptureSequenceCompleted}. + * + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @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 request targets no Surfaces or Surfaces that are not + * configured as outputs for this session; or the request + * targets a set of Surfaces that cannot be submitted + * simultaneously in a reprocessable capture session; or a + * reprocess capture request is submitted in a + * non-reprocessable capture session; or the reprocess capture + * request was created with a {@link TotalCaptureResult} from + * a different session; or the capture targets a Surface in + * the middle of being {@link #prepare prepared}; or the + * executor is null, or the listener is not null. + * + * @see #captureBurst + * @see #setRepeatingRequest + * @see #setRepeatingBurst + * @see #abortCaptures + * @see CameraDevice#createReprocessableCaptureSession + */ + public int captureSingleRequest(@NonNull CaptureRequest request, + @NonNull @CallbackExecutor Executor executor, @NonNull CaptureCallback listener) + throws CameraAccessException { + throw new UnsupportedOperationException("Subclasses must override this method"); + } + + /** * Submit a list of requests to be captured in sequence as a burst. The * burst will be captured in the minimum amount of time possible, and will * not be interleaved with requests submitted by other capture or repeat @@ -416,6 +461,53 @@ public abstract class CameraCaptureSession implements AutoCloseable { throws CameraAccessException; /** + * Submit a list of requests to be captured in sequence as a burst. The + * burst will be captured in the minimum amount of time possible, and will + * not be interleaved with requests submitted by other capture or repeat + * calls. + * + * <p>The behavior of this method matches that of + * {@link #captureBurst(List, CaptureCallback, Handler)}, + * except that it uses {@link java.util.concurrent.Executor} as an argument + * instead of {@link android.os.Handler}.</p> + * + * @param requests the list of settings for this burst capture + * @param executor the executor which will be used for invoking the listener. + * @param listener The callback object to notify each time one of the + * requests in the burst has been processed. + * + * @return int A unique capture sequence ID used by + * {@link CaptureCallback#onCaptureSequenceCompleted}. + * + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @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 the requests target + * Surfaces not currently configured as outputs; or one of the + * requests targets a set of Surfaces that cannot be submitted + * simultaneously in a reprocessable capture session; or a + * reprocess capture request is submitted in a + * non-reprocessable capture session; or one of the reprocess + * capture requests was created with a + * {@link TotalCaptureResult} from a different session; or one + * of the captures targets a Surface in the middle of being + * {@link #prepare prepared}; or if the executor is null; or if + * the listener is null. + * + * @see #capture + * @see #setRepeatingRequest + * @see #setRepeatingBurst + * @see #abortCaptures + */ + public int captureBurstRequests(@NonNull List<CaptureRequest> requests, + @NonNull @CallbackExecutor Executor executor, @NonNull CaptureCallback listener) + throws CameraAccessException { + throw new UnsupportedOperationException("Subclasses must override this method"); + } + + /** * Request endlessly repeating capture of images by this capture session. * * <p>With this method, the camera device will continually capture images @@ -483,6 +575,45 @@ public abstract class CameraCaptureSession implements AutoCloseable { throws CameraAccessException; /** + * Request endlessly repeating capture of images by this capture session. + * + * <p>The behavior of this method matches that of + * {@link #setRepeatingRequest(CaptureRequest, CaptureCallback, Handler)}, + * except that it uses {@link java.util.concurrent.Executor} as an argument + * instead of {@link android.os.Handler}.</p> + * + * @param request the request to repeat indefinitely + * @param executor the executor which will be used for invoking the listener. + * @param listener The callback object to notify every time the + * request finishes processing. + * + * @return int A unique capture sequence ID used by + * {@link CaptureCallback#onCaptureSequenceCompleted}. + * + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @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 request references no Surfaces or references Surfaces + * that are not currently configured as outputs; or the request + * is a reprocess capture request; or the capture targets a + * Surface in the middle of being {@link #prepare prepared}; or + * the executor is null; or the listener is null. + * + * @see #capture + * @see #captureBurst + * @see #setRepeatingBurst + * @see #stopRepeating + * @see #abortCaptures + */ + public int setSingleRepeatingRequest(@NonNull CaptureRequest request, + @NonNull @CallbackExecutor Executor executor, @NonNull CaptureCallback listener) + throws CameraAccessException { + throw new UnsupportedOperationException("Subclasses must override this method"); + } + + /** * <p>Request endlessly repeating capture of a sequence of images by this * capture session.</p> * @@ -555,6 +686,47 @@ public abstract class CameraCaptureSession implements AutoCloseable { throws CameraAccessException; /** + * <p>Request endlessly repeating capture of a sequence of images by this + * capture session.</p> + * + * <p>The behavior of this method matches that of + * {@link #setRepeatingBurst(List, CaptureCallback, Handler)}, + * except that it uses {@link java.util.concurrent.Executor} as an argument + * instead of {@link android.os.Handler}.</p> + * + * @param requests the list of requests to cycle through indefinitely + * @param executor the executor which will be used for invoking the listener. + * @param listener The callback object to notify each time one of the + * requests in the repeating bursts has finished processing. + * + * @return int A unique capture sequence ID used by + * {@link CaptureCallback#onCaptureSequenceCompleted}. + * + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @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 reference no Surfaces or reference Surfaces + * not currently configured as outputs; or one of the requests + * is a reprocess capture request; or one of the captures + * targets a Surface in the middle of being + * {@link #prepare prepared}; or the executor is null; or the + * listener is null. + * + * @see #capture + * @see #captureBurst + * @see #setRepeatingRequest + * @see #stopRepeating + * @see #abortCaptures + */ + public int setRepeatingBurstRequests(@NonNull List<CaptureRequest> requests, + @NonNull @CallbackExecutor Executor executor, @NonNull CaptureCallback listener) + throws CameraAccessException { + throw new UnsupportedOperationException("Subclasses must override this method"); + } + + /** * <p>Cancel any ongoing repeating capture set by either * {@link #setRepeatingRequest setRepeatingRequest} or * {@link #setRepeatingBurst}. Has no effect on requests submitted through diff --git a/android/hardware/camera2/CameraCharacteristics.java b/android/hardware/camera2/CameraCharacteristics.java index 96d043c2..4279b197 100644 --- a/android/hardware/camera2/CameraCharacteristics.java +++ b/android/hardware/camera2/CameraCharacteristics.java @@ -28,7 +28,9 @@ import android.util.Rational; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * <p>The properties describing a @@ -341,7 +343,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri */ @SuppressWarnings({"unchecked"}) public List<CaptureRequest.Key<?>> getAvailablePhysicalCameraRequestKeys() { - if (mAvailableSessionKeys == null) { + if (mAvailablePhysicalRequestKeys == null) { Object crKey = CaptureRequest.Key.class; Class<CaptureRequest.Key<?>> crKeyTyped = (Class<CaptureRequest.Key<?>>)crKey; @@ -450,23 +452,20 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri } /** - * Returns the list of physical camera ids that this logical {@link CameraDevice} is + * Returns the set of physical camera ids that this logical {@link CameraDevice} is * made up of. * * <p>A camera device is a logical camera if it has * REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA capability. If the camera device - * doesn't have the capability, the return value will be an empty list. </p> + * doesn't have the capability, the return value will be an empty set. </p> * - * <p>The list returned is not modifiable, so any attempts to modify it will throw + * <p>The set returned is not modifiable, so any attempts to modify it will throw * a {@code UnsupportedOperationException}.</p> * - * <p>Each physical camera id is only listed once in the list. The order of the keys - * is undefined.</p> - * - * @return List of physical camera ids for this logical camera device. + * @return Set of physical camera ids for this logical camera device. */ @NonNull - public List<String> getPhysicalCameraIds() { + public Set<String> getPhysicalCameraIds() { int[] availableCapabilities = get(REQUEST_AVAILABLE_CAPABILITIES); if (availableCapabilities == null) { throw new AssertionError("android.request.availableCapabilities must be non-null " @@ -475,7 +474,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri if (!ArrayUtils.contains(availableCapabilities, REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA)) { - return Collections.emptyList(); + return Collections.emptySet(); } byte[] physicalCamIds = get(LOGICAL_MULTI_CAMERA_PHYSICAL_IDS); @@ -485,9 +484,9 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri } catch (java.io.UnsupportedEncodingException e) { throw new AssertionError("android.logicalCam.physicalIds must be UTF-8 string"); } - String[] physicalCameraIdList = physicalCamIdString.split("\0"); + String[] physicalCameraIdArray = physicalCamIdString.split("\0"); - return Collections.unmodifiableList(Arrays.asList(physicalCameraIdList)); + return Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(physicalCameraIdArray))); } /*@O~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ @@ -1243,7 +1242,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * from the main sensor along the +X axis (to the right from the user's perspective) will * report <code>(0.03, 0, 0)</code>.</p> * <p>To transform a pixel coordinates between two cameras facing the same direction, first - * the source camera {@link CameraCharacteristics#LENS_RADIAL_DISTORTION android.lens.radialDistortion} must be corrected for. Then the source + * the source camera {@link CameraCharacteristics#LENS_DISTORTION android.lens.distortion} must be corrected for. Then the source * camera {@link CameraCharacteristics#LENS_INTRINSIC_CALIBRATION android.lens.intrinsicCalibration} needs to be applied, followed by the * {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation} of the source camera, the translation of the source camera * relative to the destination camera, the {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation} of the destination @@ -1257,10 +1256,10 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p><b>Units</b>: Meters</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * + * @see CameraCharacteristics#LENS_DISTORTION * @see CameraCharacteristics#LENS_INTRINSIC_CALIBRATION * @see CameraCharacteristics#LENS_POSE_REFERENCE * @see CameraCharacteristics#LENS_POSE_ROTATION - * @see CameraCharacteristics#LENS_RADIAL_DISTORTION */ @PublicKey public static final Key<float[]> LENS_POSE_TRANSLATION = @@ -1306,7 +1305,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * where <code>(0,0)</code> is the top-left of the * preCorrectionActiveArraySize rectangle. Once the pose and * intrinsic calibration transforms have been applied to a - * world point, then the {@link CameraCharacteristics#LENS_RADIAL_DISTORTION android.lens.radialDistortion} + * world point, then the {@link CameraCharacteristics#LENS_DISTORTION android.lens.distortion} * transform needs to be applied, and the result adjusted to * be in the {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} coordinate * system (where <code>(0, 0)</code> is the top-left of the @@ -1319,9 +1318,9 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * coordinate system.</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * + * @see CameraCharacteristics#LENS_DISTORTION * @see CameraCharacteristics#LENS_POSE_ROTATION * @see CameraCharacteristics#LENS_POSE_TRANSLATION - * @see CameraCharacteristics#LENS_RADIAL_DISTORTION * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE */ @@ -1363,7 +1362,14 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * * @see CameraCharacteristics#LENS_INTRINSIC_CALIBRATION + * @deprecated + * <p>This field was inconsistently defined in terms of its + * normalization. Use {@link CameraCharacteristics#LENS_DISTORTION android.lens.distortion} instead.</p> + * + * @see CameraCharacteristics#LENS_DISTORTION + */ + @Deprecated @PublicKey public static final Key<float[]> LENS_RADIAL_DISTORTION = new Key<float[]>("android.lens.radialDistortion", float[].class); @@ -1372,9 +1378,6 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>The origin for {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}.</p> * <p>Different calibration methods and use cases can produce better or worse results * depending on the selected coordinate origin.</p> - * <p>For devices designed to support the MOTION_TRACKING capability, the GYROSCOPE origin - * makes device calibration and later usage by applications combining camera and gyroscope - * information together simpler.</p> * <p><b>Possible values:</b> * <ul> * <li>{@link #LENS_POSE_REFERENCE_PRIMARY_CAMERA PRIMARY_CAMERA}</li> @@ -1391,6 +1394,46 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<Integer>("android.lens.poseReference", int.class); /** + * <p>The correction coefficients to correct for this camera device's + * radial and tangential lens distortion.</p> + * <p>Replaces the deprecated {@link CameraCharacteristics#LENS_RADIAL_DISTORTION android.lens.radialDistortion} field, which was + * inconsistently defined.</p> + * <p>Three radial distortion coefficients <code>[kappa_1, kappa_2, + * kappa_3]</code> and two tangential distortion coefficients + * <code>[kappa_4, kappa_5]</code> that can be used to correct the + * lens's geometric distortion with the mapping equations:</p> + * <pre><code> x_c = x_i * ( 1 + kappa_1 * r^2 + kappa_2 * r^4 + kappa_3 * r^6 ) + + * kappa_4 * (2 * x_i * y_i) + kappa_5 * ( r^2 + 2 * x_i^2 ) + * y_c = y_i * ( 1 + kappa_1 * r^2 + kappa_2 * r^4 + kappa_3 * r^6 ) + + * kappa_5 * (2 * x_i * y_i) + kappa_4 * ( r^2 + 2 * y_i^2 ) + * </code></pre> + * <p>Here, <code>[x_c, y_c]</code> are the coordinates to sample in the + * input image that correspond to the pixel values in the + * corrected image at the coordinate <code>[x_i, y_i]</code>:</p> + * <pre><code> correctedImage(x_i, y_i) = sample_at(x_c, y_c, inputImage) + * </code></pre> + * <p>The pixel coordinates are defined in a coordinate system + * related to the {@link CameraCharacteristics#LENS_INTRINSIC_CALIBRATION android.lens.intrinsicCalibration} + * calibration fields; see that entry for details of the mapping stages. + * Both <code>[x_i, y_i]</code> and <code>[x_c, y_c]</code> + * have <code>(0,0)</code> at the lens optical center <code>[c_x, c_y]</code>, and + * the range of the coordinates depends on the focal length + * terms of the intrinsic calibration.</p> + * <p>Finally, <code>r</code> represents the radial distance from the + * optical center, <code>r^2 = x_i^2 + y_i^2</code>.</p> + * <p>The distortion model used is the Brown-Conrady model.</p> + * <p><b>Units</b>: + * Unitless coefficients.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CameraCharacteristics#LENS_INTRINSIC_CALIBRATION + * @see CameraCharacteristics#LENS_RADIAL_DISTORTION + */ + @PublicKey + public static final Key<float[]> LENS_DISTORTION = + new Key<float[]>("android.lens.distortion", float[].class); + + /** * <p>List of noise reduction modes for {@link CaptureRequest#NOISE_REDUCTION_MODE android.noiseReduction.mode} that are supported * by this camera device.</p> * <p>Full-capability camera devices will always support OFF and FAST.</p> @@ -1422,6 +1465,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * consideration of future support.</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * @deprecated + * <p>Not used in HALv3 or newer; replaced by better partials mechanism</p> + * @hide */ @Deprecated @@ -1663,6 +1708,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO CONSTRAINED_HIGH_SPEED_VIDEO}</li> * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING MOTION_TRACKING}</li> * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA LOGICAL_MULTI_CAMERA}</li> + * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME MONOCHROME}</li> * </ul></p> * <p>This key is available on all devices.</p> * @@ -1679,6 +1725,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * @see #REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO * @see #REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING * @see #REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA + * @see #REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME */ @PublicKey public static final Key<int[]> REQUEST_AVAILABLE_CAPABILITIES = @@ -1793,11 +1840,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * The respective value of such request key can be obtained by calling * {@link CaptureRequest.Builder#getPhysicalCameraKey }. Capture requests that contain * individual physical device requests must be built via - * {@link android.hardware.camera2.CameraDevice#createCaptureRequest(int, Set)}. - * Such extended capture requests can be passed only to - * {@link CameraCaptureSession#capture } or {@link CameraCaptureSession#captureBurst } and - * not to {@link CameraCaptureSession#setRepeatingRequest } or - * {@link CameraCaptureSession#setRepeatingBurst }.</p> + * {@link android.hardware.camera2.CameraDevice#createCaptureRequest(int, Set)}.</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * <p><b>Limited capability</b> - * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the @@ -1816,6 +1859,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>When set to YUV_420_888, application can access the YUV420 data directly.</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * @deprecated + * <p>Not used in HALv3 or newer</p> + * @hide */ @Deprecated @@ -1836,6 +1881,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * TODO: Remove property.</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * @deprecated + * <p>Not used in HALv3 or newer</p> + * @hide */ @Deprecated @@ -1852,6 +1899,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE * @deprecated + * <p>Not used in HALv3 or newer</p> + * @hide */ @Deprecated @@ -1891,6 +1940,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p><b>Units</b>: Nanoseconds</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * @deprecated + * <p>Not used in HALv3 or newer</p> + * @hide */ @Deprecated @@ -1913,6 +1964,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * check if it limits the maximum size for image data.</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * @deprecated + * <p>Not used in HALv3 or newer</p> + * @hide */ @Deprecated @@ -2552,7 +2605,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.</p> * <p>The currently supported fields that correct for geometric distortion are:</p> * <ol> - * <li>{@link CameraCharacteristics#LENS_RADIAL_DISTORTION android.lens.radialDistortion}.</li> + * <li>{@link CameraCharacteristics#LENS_DISTORTION android.lens.distortion}.</li> * </ol> * <p>If all of the geometric distortion fields are no-ops, this rectangle will be the same * as the post-distortion-corrected rectangle given in @@ -2565,7 +2618,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p><b>Units</b>: Pixel coordinates on the image sensor</p> * <p>This key is available on all devices.</p> * - * @see CameraCharacteristics#LENS_RADIAL_DISTORTION + * @see CameraCharacteristics#LENS_DISTORTION * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE * @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE @@ -3341,6 +3394,21 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri public static final Key<Integer> LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE = new Key<Integer>("android.logicalMultiCamera.sensorSyncType", int.class); + /** + * <p>List of distortion correction modes for {@link CaptureRequest#DISTORTION_CORRECTION_MODE android.distortionCorrection.mode} that are + * supported by this camera device.</p> + * <p>No device is required to support this API; such devices will always list only 'OFF'. + * All devices that support this API will list both FAST and HIGH_QUALITY.</p> + * <p><b>Range of valid values:</b><br> + * Any value listed in {@link CaptureRequest#DISTORTION_CORRECTION_MODE android.distortionCorrection.mode}</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CaptureRequest#DISTORTION_CORRECTION_MODE + */ + @PublicKey + public static final Key<int[]> DISTORTION_CORRECTION_AVAILABLE_MODES = + new Key<int[]>("android.distortionCorrection.availableModes", int[].class); + /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ * End generated code *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/ diff --git a/android/hardware/camera2/CameraDevice.java b/android/hardware/camera2/CameraDevice.java index 40ee8348..f47d4640 100644 --- a/android/hardware/camera2/CameraDevice.java +++ b/android/hardware/camera2/CameraDevice.java @@ -145,37 +145,6 @@ public abstract class CameraDevice implements AutoCloseable { */ public static final int TEMPLATE_MANUAL = 6; - /** - * A template for selecting camera parameters that match TEMPLATE_PREVIEW as closely as - * possible while improving the camera output for motion tracking use cases. - * - * <p>This template is best used by applications that are frequently switching between motion - * tracking use cases and regular still capture use cases, to minimize the IQ changes - * when swapping use cases.</p> - * - * <p>This template is guaranteed to be supported on camera devices that support the - * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING MOTION_TRACKING} - * capability.</p> - * - * @see #createCaptureRequest - */ - public static final int TEMPLATE_MOTION_TRACKING_PREVIEW = 7; - - /** - * A template for selecting camera parameters that maximize the quality of camera output for - * motion tracking use cases. - * - * <p>This template is best used by applications dedicated to motion tracking applications, - * which aren't concerned about fast switches between motion tracking and other use cases.</p> - * - * <p>This template is guaranteed to be supported on camera devices that support the - * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING MOTION_TRACKING} - * capability.</p> - * - * @see #createCaptureRequest - */ - public static final int TEMPLATE_MOTION_TRACKING_BEST = 8; - /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"TEMPLATE_"}, value = @@ -184,9 +153,7 @@ public abstract class CameraDevice implements AutoCloseable { TEMPLATE_RECORD, TEMPLATE_VIDEO_SNAPSHOT, TEMPLATE_ZERO_SHUTTER_LAG, - TEMPLATE_MANUAL, - TEMPLATE_MOTION_TRACKING_PREVIEW, - TEMPLATE_MOTION_TRACKING_BEST}) + TEMPLATE_MANUAL}) public @interface RequestTemplate {}; /** @@ -420,27 +387,6 @@ public abstract class CameraDevice implements AutoCloseable { * </table><br> * </p> * - * <p>MOTION_TRACKING-capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES} - * includes - * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING MOTION_TRACKING}) - * devices support at least the below stream combinations in addition to those for - * {@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED LIMITED} devices. The - * {@code FULL FOV 640} entry means that the device will support a resolution that's 640 pixels - * wide, with the height set so that the resolution aspect ratio matches the MAXIMUM output - * aspect ratio, rounded down. So for a device with a 4:3 image sensor, this will be 640x480, - * and for a device with a 16:9 sensor, this will be 640x360, and so on. And the - * {@code MAX 30FPS} entry means the largest JPEG resolution on the device for which - * {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputMinFrameDuration} - * returns a value less than or equal to 1/30s. - * - * <table> - * <tr><th colspan="7">MOTION_TRACKING-capability additional guaranteed configurations</th></tr> - * <tr><th colspan="2" id="rb">Target 1</th><th colspan="2" id="rb">Target 2</th><th colspan="2" id="rb">Target 3</th><th rowspan="2">Sample use case(s)</th> </tr> - * <tr><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th></tr> - * <tr> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code FULL FOV 640}</td> <td>{@code JPEG}</td><td id="rb">{@code MAX 30FPS}</td> <td>Preview with a tracking YUV output and a as-large-as-possible JPEG for still captures.</td> </tr> - * </table><br> - * </p> - * * <p>BURST-capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES} includes * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE BURST_CAPTURE}) devices * support at least the below stream combinations in addition to those for @@ -873,7 +819,8 @@ public abstract class CameraDevice implements AutoCloseable { * @param config A session configuration (see {@link SessionConfiguration}). * * @throws IllegalArgumentException In case the session configuration is invalid; or the output - * configurations are empty. + * configurations are empty; or the session configuration + * executor is invalid. * @throws CameraAccessException In case the camera device is no longer connected or has * encountered a fatal error. * @see #createCaptureSession(List, CameraCaptureSession.StateCallback, Handler) @@ -917,11 +864,16 @@ public abstract class CameraDevice implements AutoCloseable { * request for a specific physical camera. The settings are chosen * to be the best options for the specific logical camera device. If * additional physical camera ids are passed, then they will also use the - * same settings template. Requests containing individual physical camera - * settings can be passed only to {@link CameraCaptureSession#capture} or - * {@link CameraCaptureSession#captureBurst} and not to - * {@link CameraCaptureSession#setRepeatingRequest} or - * {@link CameraCaptureSession#setRepeatingBurst}</p> + * same settings template. Clients can further modify individual camera + * settings by calling {@link CaptureRequest.Builder#setPhysicalCameraKey}.</p> + * + * <p>Individual physical camera settings will only be honored for camera session + * that was initialiazed with corresponding physical camera id output configuration + * {@link OutputConfiguration#setPhysicalCameraId} and the same output targets are + * also attached in the request by {@link CaptureRequest.Builder#addTarget}.</p> + * + * <p>The output is undefined for any logical camera streams in case valid physical camera + * settings are attached.</p> * * @param templateType An enumeration selecting the use case for this request. Not all template * types are supported on every device. See the documentation for each template type for @@ -942,8 +894,8 @@ public abstract class CameraDevice implements AutoCloseable { * @see #TEMPLATE_STILL_CAPTURE * @see #TEMPLATE_VIDEO_SNAPSHOT * @see #TEMPLATE_MANUAL - * @see CaptureRequest.Builder#setKey - * @see CaptureRequest.Builder#getKey + * @see CaptureRequest.Builder#setPhysicalCameraKey + * @see CaptureRequest.Builder#getPhysicalCameraKey */ @NonNull public CaptureRequest.Builder createCaptureRequest(@RequestTemplate int templateType, diff --git a/android/hardware/camera2/CameraManager.java b/android/hardware/camera2/CameraManager.java index a2bc91e0..4124536d 100644 --- a/android/hardware/camera2/CameraManager.java +++ b/android/hardware/camera2/CameraManager.java @@ -16,6 +16,7 @@ package android.hardware.camera2; +import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -25,6 +26,7 @@ import android.hardware.CameraInfo; import android.hardware.CameraStatus; import android.hardware.ICameraService; import android.hardware.ICameraServiceListener; +import android.hardware.camera2.impl.CameraDeviceImpl; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.legacy.CameraDeviceUserShim; import android.hardware.camera2.legacy.LegacyMetadataMapper; @@ -41,6 +43,11 @@ import android.util.ArrayMap; import android.util.Log; import java.util.ArrayList; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; /** * <p>A system service manager for detecting, characterizing, and connecting to @@ -123,16 +130,29 @@ public final class CameraManager { */ public void registerAvailabilityCallback(@NonNull AvailabilityCallback callback, @Nullable Handler handler) { - if (handler == null) { - Looper looper = Looper.myLooper(); - if (looper == null) { - throw new IllegalArgumentException( - "No handler given, and current thread has no looper!"); - } - handler = new Handler(looper); - } + CameraManagerGlobal.get().registerAvailabilityCallback(callback, + CameraDeviceImpl.checkAndWrapHandler(handler)); + } - CameraManagerGlobal.get().registerAvailabilityCallback(callback, handler); + /** + * Register a callback to be notified about camera device availability. + * + * <p>The behavior of this method matches that of + * {@link #registerAvailabilityCallback(AvailabilityCallback, Handler)}, + * except that it uses {@link java.util.concurrent.Executor} as an argument + * instead of {@link android.os.Handler}.</p> + * + * @param executor The executor which will be used to invoke the callback. + * @param callback the new callback to send camera availability notices to + * + * @throws IllegalArgumentException if the executor is {@code null}. + */ + public void registerAvailabilityCallback(@NonNull @CallbackExecutor Executor executor, + @NonNull AvailabilityCallback callback) { + if (executor == null) { + throw new IllegalArgumentException("executor was null"); + } + CameraManagerGlobal.get().registerAvailabilityCallback(callback, executor); } /** @@ -170,15 +190,29 @@ public final class CameraManager { * no looper. */ public void registerTorchCallback(@NonNull TorchCallback callback, @Nullable Handler handler) { - if (handler == null) { - Looper looper = Looper.myLooper(); - if (looper == null) { - throw new IllegalArgumentException( - "No handler given, and current thread has no looper!"); - } - handler = new Handler(looper); + CameraManagerGlobal.get().registerTorchCallback(callback, + CameraDeviceImpl.checkAndWrapHandler(handler)); + } + + /** + * Register a callback to be notified about torch mode status. + * + * <p>The behavior of this method matches that of + * {@link #registerTorchCallback(TorchCallback, Handler)}, + * except that it uses {@link java.util.concurrent.Executor} as an argument + * instead of {@link android.os.Handler}.</p> + * + * @param executor The executor which will be used to invoke the callback + * @param callback The new callback to send torch mode status to + * + * @throws IllegalArgumentException if the executor is {@code null}. + */ + public void registerTorchCallback(@NonNull @CallbackExecutor Executor executor, + @NonNull TorchCallback callback) { + if (executor == null) { + throw new IllegalArgumentException("executor was null"); } - CameraManagerGlobal.get().registerTorchCallback(callback, handler); + CameraManagerGlobal.get().registerTorchCallback(callback, executor); } /** @@ -257,7 +291,7 @@ public final class CameraManager { * * @param cameraId The unique identifier of the camera device to open * @param callback The callback for the camera. Must not be null. - * @param handler The handler to invoke the callback on. Must not be null. + * @param executor The executor to invoke the callback with. Must not be null. * @param uid The UID of the application actually opening the camera. * Must be USE_CALLING_UID unless the caller is a service * that is trusted to open the device on behalf of an @@ -276,7 +310,7 @@ public final class CameraManager { * @see android.app.admin.DevicePolicyManager#setCameraDisabled */ private CameraDevice openCameraDeviceUserAsync(String cameraId, - CameraDevice.StateCallback callback, Handler handler, final int uid) + CameraDevice.StateCallback callback, Executor executor, final int uid) throws CameraAccessException { CameraCharacteristics characteristics = getCameraCharacteristics(cameraId); CameraDevice device = null; @@ -289,7 +323,7 @@ public final class CameraManager { new android.hardware.camera2.impl.CameraDeviceImpl( cameraId, callback, - handler, + executor, characteristics, mContext.getApplicationInfo().targetSdkVersion); @@ -430,7 +464,47 @@ public final class CameraManager { @NonNull final CameraDevice.StateCallback callback, @Nullable Handler handler) throws CameraAccessException { - openCameraForUid(cameraId, callback, handler, USE_CALLING_UID); + openCameraForUid(cameraId, callback, CameraDeviceImpl.checkAndWrapHandler(handler), + USE_CALLING_UID); + } + + /** + * Open a connection to a camera with the given ID. + * + * <p>The behavior of this method matches that of + * {@link #openCamera(String, StateCallback, Handler)}, except that it uses + * {@link java.util.concurrent.Executor} as an argument instead of + * {@link android.os.Handler}.</p> + * + * @param cameraId + * The unique identifier of the camera device to open + * @param executor + * The executor which will be used when invoking the callback. + * @param callback + * The callback which is invoked once the camera is opened + * + * @throws CameraAccessException if the camera is disabled by device policy, + * has been disconnected, or is being used by a higher-priority camera API client. + * + * @throws IllegalArgumentException if cameraId, the callback or the executor was null, + * or the cameraId does not match any currently or previously available + * camera device. + * + * @throws SecurityException if the application does not have permission to + * access the camera + * + * @see #getCameraIdList + * @see android.app.admin.DevicePolicyManager#setCameraDisabled + */ + @RequiresPermission(android.Manifest.permission.CAMERA) + public void openCamera(@NonNull String cameraId, + @NonNull @CallbackExecutor Executor executor, + @NonNull final CameraDevice.StateCallback callback) + throws CameraAccessException { + if (executor == null) { + throw new IllegalArgumentException("executor was null"); + } + openCameraForUid(cameraId, callback, executor, USE_CALLING_UID); } /** @@ -449,7 +523,7 @@ public final class CameraManager { * @hide */ public void openCameraForUid(@NonNull String cameraId, - @NonNull final CameraDevice.StateCallback callback, @Nullable Handler handler, + @NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor, int clientUid) throws CameraAccessException { @@ -457,19 +531,12 @@ public final class CameraManager { throw new IllegalArgumentException("cameraId was null"); } else if (callback == null) { throw new IllegalArgumentException("callback was null"); - } else if (handler == null) { - if (Looper.myLooper() != null) { - handler = new Handler(); - } else { - throw new IllegalArgumentException( - "Handler argument is null, but no looper exists in the calling thread"); - } } if (CameraManagerGlobal.sCameraServiceDisabled) { throw new IllegalArgumentException("No cameras available on device"); } - openCameraDeviceUserAsync(cameraId, callback, handler, clientUid); + openCameraDeviceUserAsync(cameraId, callback, executor, clientUid); } /** @@ -728,12 +795,13 @@ public final class CameraManager { */ private static final String CAMERA_SERVICE_BINDER_NAME = "media.camera"; + private final ScheduledExecutorService mScheduler = Executors.newScheduledThreadPool(1); // Camera ID -> Status map private final ArrayMap<String, Integer> mDeviceStatus = new ArrayMap<String, Integer>(); - // Registered availablility callbacks and their handlers - private final ArrayMap<AvailabilityCallback, Handler> mCallbackMap = - new ArrayMap<AvailabilityCallback, Handler>(); + // Registered availablility callbacks and their executors + private final ArrayMap<AvailabilityCallback, Executor> mCallbackMap = + new ArrayMap<AvailabilityCallback, Executor>(); // torch client binder to set the torch mode with. private Binder mTorchClientBinder = new Binder(); @@ -741,9 +809,9 @@ public final class CameraManager { // Camera ID -> Torch status map private final ArrayMap<String, Integer> mTorchStatus = new ArrayMap<String, Integer>(); - // Registered torch callbacks and their handlers - private final ArrayMap<TorchCallback, Handler> mTorchCallbackMap = - new ArrayMap<TorchCallback, Handler>(); + // Registered torch callbacks and their executors + private final ArrayMap<TorchCallback, Executor> mTorchCallbackMap = + new ArrayMap<TorchCallback, Executor>(); private final Object mLock = new Object(); @@ -925,49 +993,63 @@ public final class CameraManager { } } - private void postSingleUpdate(final AvailabilityCallback callback, final Handler handler, + private void postSingleUpdate(final AvailabilityCallback callback, final Executor executor, final String id, final int status) { if (isAvailable(status)) { - handler.post( - new Runnable() { - @Override - public void run() { - callback.onCameraAvailable(id); - } - }); + final long ident = Binder.clearCallingIdentity(); + try { + executor.execute( + new Runnable() { + @Override + public void run() { + callback.onCameraAvailable(id); + } + }); + } finally { + Binder.restoreCallingIdentity(ident); + } } else { - handler.post( - new Runnable() { - @Override - public void run() { - callback.onCameraUnavailable(id); - } - }); + final long ident = Binder.clearCallingIdentity(); + try { + executor.execute( + new Runnable() { + @Override + public void run() { + callback.onCameraUnavailable(id); + } + }); + } finally { + Binder.restoreCallingIdentity(ident); + } } } - private void postSingleTorchUpdate(final TorchCallback callback, final Handler handler, + private void postSingleTorchUpdate(final TorchCallback callback, final Executor executor, final String id, final int status) { switch(status) { case ICameraServiceListener.TORCH_STATUS_AVAILABLE_ON: - case ICameraServiceListener.TORCH_STATUS_AVAILABLE_OFF: - handler.post( - new Runnable() { - @Override - public void run() { - callback.onTorchModeChanged(id, status == - ICameraServiceListener.TORCH_STATUS_AVAILABLE_ON); - } + case ICameraServiceListener.TORCH_STATUS_AVAILABLE_OFF: { + final long ident = Binder.clearCallingIdentity(); + try { + executor.execute(() -> { + callback.onTorchModeChanged(id, status == + ICameraServiceListener.TORCH_STATUS_AVAILABLE_ON); }); + } finally { + Binder.restoreCallingIdentity(ident); + } + } break; - default: - handler.post( - new Runnable() { - @Override - public void run() { - callback.onTorchModeUnavailable(id); - } + default: { + final long ident = Binder.clearCallingIdentity(); + try { + executor.execute(() -> { + callback.onTorchModeUnavailable(id); }); + } finally { + Binder.restoreCallingIdentity(ident); + } + } break; } } @@ -976,11 +1058,11 @@ public final class CameraManager { * Send the state of all known cameras to the provided listener, to initialize * the listener's knowledge of camera state. */ - private void updateCallbackLocked(AvailabilityCallback callback, Handler handler) { + private void updateCallbackLocked(AvailabilityCallback callback, Executor executor) { for (int i = 0; i < mDeviceStatus.size(); i++) { String id = mDeviceStatus.keyAt(i); Integer status = mDeviceStatus.valueAt(i); - postSingleUpdate(callback, handler, id, status); + postSingleUpdate(callback, executor, id, status); } } @@ -1039,18 +1121,18 @@ public final class CameraManager { final int callbackCount = mCallbackMap.size(); for (int i = 0; i < callbackCount; i++) { - Handler handler = mCallbackMap.valueAt(i); + Executor executor = mCallbackMap.valueAt(i); final AvailabilityCallback callback = mCallbackMap.keyAt(i); - postSingleUpdate(callback, handler, id, status); + postSingleUpdate(callback, executor, id, status); } } // onStatusChangedLocked - private void updateTorchCallbackLocked(TorchCallback callback, Handler handler) { + private void updateTorchCallbackLocked(TorchCallback callback, Executor executor) { for (int i = 0; i < mTorchStatus.size(); i++) { String id = mTorchStatus.keyAt(i); Integer status = mTorchStatus.valueAt(i); - postSingleTorchUpdate(callback, handler, id, status); + postSingleTorchUpdate(callback, executor, id, status); } } @@ -1078,9 +1160,9 @@ public final class CameraManager { final int callbackCount = mTorchCallbackMap.size(); for (int i = 0; i < callbackCount; i++) { - final Handler handler = mTorchCallbackMap.valueAt(i); + final Executor executor = mTorchCallbackMap.valueAt(i); final TorchCallback callback = mTorchCallbackMap.keyAt(i); - postSingleTorchUpdate(callback, handler, id, status); + postSingleTorchUpdate(callback, executor, id, status); } } // onTorchStatusChangedLocked @@ -1089,16 +1171,16 @@ public final class CameraManager { * global listener singleton. * * @param callback the new callback to send camera availability notices to - * @param handler The handler on which the callback should be invoked. May not be null. + * @param executor The executor which should invoke the callback. May not be null. */ - public void registerAvailabilityCallback(AvailabilityCallback callback, Handler handler) { + public void registerAvailabilityCallback(AvailabilityCallback callback, Executor executor) { synchronized (mLock) { connectCameraServiceLocked(); - Handler oldHandler = mCallbackMap.put(callback, handler); + Executor oldExecutor = mCallbackMap.put(callback, executor); // For new callbacks, provide initial availability information - if (oldHandler == null) { - updateCallbackLocked(callback, handler); + if (oldExecutor == null) { + updateCallbackLocked(callback, executor); } // If not connected to camera service, schedule a reconnect to camera service. @@ -1120,14 +1202,14 @@ public final class CameraManager { } } - public void registerTorchCallback(TorchCallback callback, Handler handler) { + public void registerTorchCallback(TorchCallback callback, Executor executor) { synchronized(mLock) { connectCameraServiceLocked(); - Handler oldHandler = mTorchCallbackMap.put(callback, handler); + Executor oldExecutor = mTorchCallbackMap.put(callback, executor); // For new callbacks, provide initial torch information - if (oldHandler == null) { - updateTorchCallbackLocked(callback, handler); + if (oldExecutor == null) { + updateTorchCallbackLocked(callback, executor); } // If not connected to camera service, schedule a reconnect to camera service. @@ -1165,13 +1247,7 @@ public final class CameraManager { * availability callback or torch status callback. */ private void scheduleCameraServiceReconnectionLocked() { - final Handler handler; - - if (mCallbackMap.size() > 0) { - handler = mCallbackMap.valueAt(0); - } else if (mTorchCallbackMap.size() > 0) { - handler = mTorchCallbackMap.valueAt(0); - } else { + if (mCallbackMap.isEmpty() && mTorchCallbackMap.isEmpty()) { // Not necessary to reconnect camera service if no client registers a callback. return; } @@ -1181,22 +1257,21 @@ public final class CameraManager { " ms"); } - handler.postDelayed( - new Runnable() { - @Override - public void run() { - ICameraService cameraService = getCameraService(); - if (cameraService == null) { - synchronized(mLock) { - if (DEBUG) { - Log.v(TAG, "Reconnecting Camera Service failed."); - } - scheduleCameraServiceReconnectionLocked(); - } + try { + mScheduler.schedule(() -> { + ICameraService cameraService = getCameraService(); + if (cameraService == null) { + synchronized(mLock) { + if (DEBUG) { + Log.v(TAG, "Reconnecting Camera Service failed."); } + scheduleCameraServiceReconnectionLocked(); } - }, - CAMERA_SERVICE_RECONNECT_DELAY_MS); + } + }, CAMERA_SERVICE_RECONNECT_DELAY_MS, TimeUnit.MILLISECONDS); + } catch (RejectedExecutionException e) { + Log.e(TAG, "Failed to schedule camera service re-connect: " + e); + } } /** diff --git a/android/hardware/camera2/CameraMetadata.java b/android/hardware/camera2/CameraMetadata.java index e7c89611..1a5d3ac9 100644 --- a/android/hardware/camera2/CameraMetadata.java +++ b/android/hardware/camera2/CameraMetadata.java @@ -342,7 +342,7 @@ public abstract class CameraMetadata<TKey> { /** * <p>The value of {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation} is relative to the optical center of * the largest camera device facing the same direction as this camera.</p> - * <p>This default value for API levels before Android P.</p> + * <p>This is the default value for API levels before Android P.</p> * * @see CameraCharacteristics#LENS_POSE_TRANSLATION * @see CameraCharacteristics#LENS_POSE_REFERENCE @@ -352,7 +352,6 @@ public abstract class CameraMetadata<TKey> { /** * <p>The value of {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation} is relative to the position of the * primary gyroscope of this Android device.</p> - * <p>This is the value reported by all devices that support the MOTION_TRACKING capability.</p> * * @see CameraCharacteristics#LENS_POSE_TRANSLATION * @see CameraCharacteristics#LENS_POSE_REFERENCE @@ -685,7 +684,7 @@ public abstract class CameraMetadata<TKey> { * <li>{@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}</li> * <li>{@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}</li> * <li>{@link CameraCharacteristics#LENS_INTRINSIC_CALIBRATION android.lens.intrinsicCalibration}</li> - * <li>{@link CameraCharacteristics#LENS_RADIAL_DISTORTION android.lens.radialDistortion}</li> + * <li>{@link CameraCharacteristics#LENS_DISTORTION android.lens.distortion}</li> * </ul> * </li> * <li>The {@link CameraCharacteristics#DEPTH_DEPTH_IS_EXCLUSIVE android.depth.depthIsExclusive} entry is listed by this device.</li> @@ -703,12 +702,12 @@ public abstract class CameraMetadata<TKey> { * rate, including depth stall time.</p> * * @see CameraCharacteristics#DEPTH_DEPTH_IS_EXCLUSIVE + * @see CameraCharacteristics#LENS_DISTORTION * @see CameraCharacteristics#LENS_FACING * @see CameraCharacteristics#LENS_INTRINSIC_CALIBRATION * @see CameraCharacteristics#LENS_POSE_REFERENCE * @see CameraCharacteristics#LENS_POSE_ROTATION * @see CameraCharacteristics#LENS_POSE_TRANSLATION - * @see CameraCharacteristics#LENS_RADIAL_DISTORTION * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES */ public static final int REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT = 8; @@ -801,46 +800,12 @@ public abstract class CameraMetadata<TKey> { public static final int REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO = 9; /** - * <p>The device supports controls and metadata required for accurate motion tracking for - * use cases such as augmented reality, electronic image stabilization, and so on.</p> - * <p>This means this camera device has accurate optical calibration and timestamps relative - * to the inertial sensors.</p> - * <p>This capability requires the camera device to support the following:</p> - * <ul> - * <li>Capture request templates {@link android.hardware.camera2.CameraDevice#TEMPLATE_MOTION_TRACKING_PREVIEW } and {@link android.hardware.camera2.CameraDevice#TEMPLATE_MOTION_TRACKING_BEST } are defined.</li> - * <li>The stream configurations listed in {@link android.hardware.camera2.CameraDevice#createCaptureSession } for MOTION_TRACKING are - * supported, either at 30 or 60fps maximum frame rate.</li> - * <li>The following camera characteristics and capture result metadata are provided:<ul> - * <li>{@link CameraCharacteristics#LENS_INTRINSIC_CALIBRATION android.lens.intrinsicCalibration}</li> - * <li>{@link CameraCharacteristics#LENS_RADIAL_DISTORTION android.lens.radialDistortion}</li> - * <li>{@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}</li> - * <li>{@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}</li> - * <li>{@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference} with value GYROSCOPE</li> - * </ul> - * </li> - * <li>The {@link CameraCharacteristics#SENSOR_INFO_TIMESTAMP_SOURCE android.sensor.info.timestampSource} field has value <code>REALTIME</code>. When compared to - * timestamps from the device's gyroscopes, the clock difference for events occuring at - * the same actual time instant will be less than 1 ms.</li> - * <li>The value of the {@link CaptureResult#SENSOR_ROLLING_SHUTTER_SKEW android.sensor.rollingShutterSkew} field is accurate to within 1 ms.</li> - * <li>The value of {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime} is guaranteed to be available in the - * capture result.</li> - * <li>The {@link CaptureRequest#CONTROL_CAPTURE_INTENT android.control.captureIntent} control supports MOTION_TRACKING to limit maximum - * exposure to 20 milliseconds.</li> - * <li>The stream configurations required for MOTION_TRACKING (listed at {@link android.hardware.camera2.CameraDevice#createCaptureSession }) can operate at least at - * 30fps; optionally, they can operate at 60fps, and '[60, 60]' is listed in - * {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES android.control.aeAvailableTargetFpsRanges}.</li> - * </ul> + * <p>The camera device supports the MOTION_TRACKING value for + * {@link CaptureRequest#CONTROL_CAPTURE_INTENT android.control.captureIntent}, which limits maximum exposure time to 20 ms.</p> + * <p>This limits the motion blur of capture images, resulting in better image tracking + * results for use cases such as image stabilization or augmented reality.</p> * - * @see CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES * @see CaptureRequest#CONTROL_CAPTURE_INTENT - * @see CameraCharacteristics#LENS_INTRINSIC_CALIBRATION - * @see CameraCharacteristics#LENS_POSE_REFERENCE - * @see CameraCharacteristics#LENS_POSE_ROTATION - * @see CameraCharacteristics#LENS_POSE_TRANSLATION - * @see CameraCharacteristics#LENS_RADIAL_DISTORTION - * @see CaptureRequest#SENSOR_EXPOSURE_TIME - * @see CameraCharacteristics#SENSOR_INFO_TIMESTAMP_SOURCE - * @see CaptureResult#SENSOR_ROLLING_SHUTTER_SKEW * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES */ public static final int REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING = 10; @@ -861,37 +826,49 @@ public abstract class CameraMetadata<TKey> { * <li>{@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}</li> * <li>{@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}</li> * <li>{@link CameraCharacteristics#LENS_INTRINSIC_CALIBRATION android.lens.intrinsicCalibration}</li> - * <li>{@link CameraCharacteristics#LENS_RADIAL_DISTORTION android.lens.radialDistortion}</li> + * <li>{@link CameraCharacteristics#LENS_DISTORTION android.lens.distortion}</li> * </ul> * </li> + * <li>The SENSOR_INFO_TIMESTAMP_SOURCE of the logical device and physical devices must be + * the same.</li> * <li>The logical camera device must be LIMITED or higher device.</li> * </ul> * <p>Both the logical camera device and its underlying physical devices support the * mandatory stream combinations required for their device levels.</p> * <p>Additionally, for each guaranteed stream combination, the logical camera supports:</p> * <ul> - * <li>Replacing one logical {@link android.graphics.ImageFormat#YUV_420_888 YUV_420_888} + * <li>For each guaranteed stream combination, the logical camera supports replacing one + * logical {@link android.graphics.ImageFormat#YUV_420_888 YUV_420_888} * or raw stream with two physical streams of the same size and format, each from a * separate physical camera, given that the size and format are supported by both * physical cameras.</li> - * <li>Adding two raw streams, each from one physical camera, if the logical camera doesn't - * advertise RAW capability, but the underlying physical cameras do. This is usually - * the case when the physical cameras have different sensor sizes.</li> + * <li>If the logical camera doesn't advertise RAW capability, but the underlying physical + * cameras do, the logical camera will support guaranteed stream combinations for RAW + * capability, except that the RAW streams will be physical streams, each from a separate + * physical camera. This is usually the case when the physical cameras have different + * sensor sizes.</li> * </ul> * <p>Using physical streams in place of a logical stream of the same size and format will * not slow down the frame rate of the capture, as long as the minimum frame duration * of the physical and logical streams are the same.</p> * + * @see CameraCharacteristics#LENS_DISTORTION * @see CameraCharacteristics#LENS_INTRINSIC_CALIBRATION * @see CameraCharacteristics#LENS_POSE_REFERENCE * @see CameraCharacteristics#LENS_POSE_ROTATION * @see CameraCharacteristics#LENS_POSE_TRANSLATION - * @see CameraCharacteristics#LENS_RADIAL_DISTORTION * @see CameraCharacteristics#LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES */ public static final int REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA = 11; + /** + * <p>The camera device is a monochrome camera that doesn't contain a color filter array, + * and the pixel values on U and Y planes are all 128.</p> + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + */ + public static final int REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME = 12; + // // Enumeration values for CameraCharacteristics#SCALER_CROPPING_TYPE // @@ -1465,9 +1442,11 @@ public abstract class CameraMetadata<TKey> { * for the external flash. Otherwise, this mode acts like ON.</p> * <p>When the external flash is turned off, AE mode should be changed to one of the * other available AE modes.</p> - * <p>If the camera device supports AE external flash mode, aeState must be - * FLASH_REQUIRED after the camera device finishes AE scan and it's too dark without + * <p>If the camera device supports AE external flash mode, {@link CaptureResult#CONTROL_AE_STATE android.control.aeState} must + * be FLASH_REQUIRED after the camera device finishes AE scan and it's too dark without * flash.</p> + * + * @see CaptureResult#CONTROL_AE_STATE * @see CaptureRequest#CONTROL_AE_MODE */ public static final int CONTROL_AE_MODE_ON_EXTERNAL_FLASH = 5; @@ -2676,6 +2655,10 @@ public abstract class CameraMetadata<TKey> { /** * <p>Include OIS data in the capture result.</p> + * <p>{@link CaptureResult#STATISTICS_OIS_SAMPLES android.statistics.oisSamples} provides OIS sample data in the + * output result metadata.</p> + * + * @see CaptureResult#STATISTICS_OIS_SAMPLES * @see CaptureRequest#STATISTICS_OIS_DATA_MODE */ public static final int STATISTICS_OIS_DATA_MODE_ON = 1; @@ -2754,6 +2737,31 @@ public abstract class CameraMetadata<TKey> { public static final int TONEMAP_PRESET_CURVE_REC709 = 1; // + // Enumeration values for CaptureRequest#DISTORTION_CORRECTION_MODE + // + + /** + * <p>No distortion correction is applied.</p> + * @see CaptureRequest#DISTORTION_CORRECTION_MODE + */ + public static final int DISTORTION_CORRECTION_MODE_OFF = 0; + + /** + * <p>Lens distortion correction is applied without reducing frame rate + * relative to sensor output. It may be the same as OFF if distortion correction would + * reduce frame rate relative to sensor.</p> + * @see CaptureRequest#DISTORTION_CORRECTION_MODE + */ + public static final int DISTORTION_CORRECTION_MODE_FAST = 1; + + /** + * <p>High-quality distortion correction is applied, at the cost of + * possibly reduced frame rate relative to sensor output.</p> + * @see CaptureRequest#DISTORTION_CORRECTION_MODE + */ + public static final int DISTORTION_CORRECTION_MODE_HIGH_QUALITY = 2; + + // // Enumeration values for CaptureResult#CONTROL_AE_STATE // diff --git a/android/hardware/camera2/CaptureRequest.java b/android/hardware/camera2/CaptureRequest.java index 481b7649..22525719 100644 --- a/android/hardware/camera2/CaptureRequest.java +++ b/android/hardware/camera2/CaptureRequest.java @@ -24,6 +24,7 @@ import android.hardware.camera2.impl.SyntheticKey; import android.hardware.camera2.params.OutputConfiguration; import android.hardware.camera2.utils.HashCodeHelpers; import android.hardware.camera2.utils.TypeReference; +import android.hardware.camera2.utils.SurfaceUtils; import android.os.Parcel; import android.os.Parcelable; import android.util.ArraySet; @@ -643,6 +644,30 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> break; } } + + if (!streamFound) { + // Check if we can match s by native object ID + long reqSurfaceId = SurfaceUtils.getSurfaceId(s); + for (int j = 0; j < configuredOutputs.size(); ++j) { + int streamId = configuredOutputs.keyAt(j); + OutputConfiguration outConfig = configuredOutputs.valueAt(j); + int surfaceId = 0; + for (Surface outSurface : outConfig.getSurfaces()) { + if (reqSurfaceId == SurfaceUtils.getSurfaceId(outSurface)) { + streamFound = true; + mStreamIdxArray[i] = streamId; + mSurfaceIdxArray[i] = surfaceId; + i++; + break; + } + surfaceId++; + } + if (streamFound) { + break; + } + } + } + if (!streamFound) { mStreamIdxArray = null; mSurfaceIdxArray = null; @@ -783,7 +808,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * *<p>This method can be called for logical camera devices, which are devices that have * REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA capability and calls to - * {@link CameraCharacteristics#getPhysicalCameraIds} return a non-empty list of + * {@link CameraCharacteristics#getPhysicalCameraIds} return a non-empty set of * physical devices that are backing the logical camera. The camera Id included in the * 'physicalCameraId' argument selects an individual physical device that will receive * the customized capture request field.</p> @@ -1432,7 +1457,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * is used, all non-zero weights will have the same effect. A region with 0 weight is * ignored.</p> * <p>If all regions have 0 weight, then no specific metering area needs to be used by the - * camera device.</p> + * camera device. The capture result will either be a zero weight region as well, or + * the region selected by the camera device as the focus area of interest.</p> * <p>If the metering region is outside the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in * capture result metadata, the camera device will ignore the sections outside the crop * region and output only the intersection rectangle as the metering region in the result @@ -2757,21 +2783,18 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> new Key<Integer>("android.statistics.lensShadingMapMode", int.class); /** - * <p>Whether the camera device outputs the OIS data in output + * <p>A control for selecting whether OIS position information is included in output * result metadata.</p> - * <p>When set to ON, - * {@link CaptureResult#STATISTICS_OIS_TIMESTAMPS android.statistics.oisTimestamps}, android.statistics.oisShiftPixelX, - * android.statistics.oisShiftPixelY will provide OIS data in the output result metadata.</p> * <p><b>Possible values:</b> * <ul> * <li>{@link #STATISTICS_OIS_DATA_MODE_OFF OFF}</li> * <li>{@link #STATISTICS_OIS_DATA_MODE_ON ON}</li> * </ul></p> * <p><b>Available values for this device:</b><br> - * android.Statistics.info.availableOisDataModes</p> + * {@link CameraCharacteristics#STATISTICS_INFO_AVAILABLE_OIS_DATA_MODES android.statistics.info.availableOisDataModes}</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * - * @see CaptureResult#STATISTICS_OIS_TIMESTAMPS + * @see CameraCharacteristics#STATISTICS_INFO_AVAILABLE_OIS_DATA_MODES * @see #STATISTICS_OIS_DATA_MODE_OFF * @see #STATISTICS_OIS_DATA_MODE_ON */ @@ -2830,6 +2853,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * of points can be less than max (that is, the request doesn't have to * always provide a curve with number of points equivalent to * {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}).</p> + * <p>For devices with MONOCHROME capability, only red channel is used. Green and blue channels + * are ignored.</p> * <p>A few examples, and their corresponding graphical mappings; these * only specify the red channel and the precision is limited to 4 * digits, for conciseness.</p> @@ -2892,6 +2917,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * of points can be less than max (that is, the request doesn't have to * always provide a curve with number of points equivalent to * {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}).</p> + * <p>For devices with MONOCHROME capability, only red channel is used. Green and blue channels + * are ignored.</p> * <p>A few examples, and their corresponding graphical mappings; these * only specify the red channel and the precision is limited to 4 * digits, for conciseness.</p> @@ -3144,6 +3171,49 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> public static final Key<Float> REPROCESS_EFFECTIVE_EXPOSURE_FACTOR = new Key<Float>("android.reprocess.effectiveExposureFactor", float.class); + /** + * <p>Mode of operation for the lens distortion correction block.</p> + * <p>The lens distortion correction block attempts to improve image quality by fixing + * radial, tangential, or other geometric aberrations in the camera device's optics. If + * available, the {@link CameraCharacteristics#LENS_DISTORTION android.lens.distortion} field documents the lens's distortion parameters.</p> + * <p>OFF means no distortion correction is done.</p> + * <p>FAST/HIGH_QUALITY both mean camera device determined distortion correction will be + * applied. HIGH_QUALITY mode indicates that the camera device will use the highest-quality + * correction algorithms, even if it slows down capture rate. FAST means the camera device + * will not slow down capture rate when applying correction. FAST may be the same as OFF if + * any correction at all would slow down capture rate. Every output stream will have a + * similar amount of enhancement applied.</p> + * <p>The correction only applies to processed outputs such as YUV, JPEG, or DEPTH16; it is not + * applied to any RAW output. Metadata coordinates such as face rectangles or metering + * regions are also not affected by correction.</p> + * <p>Applications enabling distortion correction need to pay extra attention when converting + * image coordinates between corrected output buffers and the sensor array. For example, if + * the app supports tap-to-focus and enables correction, it then has to apply the distortion + * model described in {@link CameraCharacteristics#LENS_DISTORTION android.lens.distortion} to the image buffer tap coordinates to properly + * calculate the tap position on the sensor active array to be used with + * {@link CaptureRequest#CONTROL_AF_REGIONS android.control.afRegions}. The same applies in reverse to detected face rectangles if + * they need to be drawn on top of the corrected output buffers.</p> + * <p><b>Possible values:</b> + * <ul> + * <li>{@link #DISTORTION_CORRECTION_MODE_OFF OFF}</li> + * <li>{@link #DISTORTION_CORRECTION_MODE_FAST FAST}</li> + * <li>{@link #DISTORTION_CORRECTION_MODE_HIGH_QUALITY HIGH_QUALITY}</li> + * </ul></p> + * <p><b>Available values for this device:</b><br> + * {@link CameraCharacteristics#DISTORTION_CORRECTION_AVAILABLE_MODES android.distortionCorrection.availableModes}</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CaptureRequest#CONTROL_AF_REGIONS + * @see CameraCharacteristics#DISTORTION_CORRECTION_AVAILABLE_MODES + * @see CameraCharacteristics#LENS_DISTORTION + * @see #DISTORTION_CORRECTION_MODE_OFF + * @see #DISTORTION_CORRECTION_MODE_FAST + * @see #DISTORTION_CORRECTION_MODE_HIGH_QUALITY + */ + @PublicKey + public static final Key<Integer> DISTORTION_CORRECTION_MODE = + new Key<Integer>("android.distortionCorrection.mode", int.class); + /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ * End generated code *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/ diff --git a/android/hardware/camera2/CaptureResult.java b/android/hardware/camera2/CaptureResult.java index d730fa8a..8df54472 100644 --- a/android/hardware/camera2/CaptureResult.java +++ b/android/hardware/camera2/CaptureResult.java @@ -1001,8 +1001,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * </tbody> * </table> * <p>If the camera device supports AE external flash mode (ON_EXTERNAL_FLASH is included in - * {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_MODES android.control.aeAvailableModes}), aeState must be FLASH_REQUIRED after the camera device - * finishes AE scan and it's too dark without flash.</p> + * {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_MODES android.control.aeAvailableModes}), {@link CaptureResult#CONTROL_AE_STATE android.control.aeState} must be FLASH_REQUIRED after + * the camera device finishes AE scan and it's too dark without flash.</p> * <p>For the above table, the camera device may skip reporting any state changes that happen * without application intervention (i.e. mode switch, trigger, locking). Any state that * can be skipped in that manner is called a transient state.</p> @@ -1081,6 +1081,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * @see CaptureRequest#CONTROL_AE_LOCK * @see CaptureRequest#CONTROL_AE_MODE * @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER + * @see CaptureResult#CONTROL_AE_STATE * @see CaptureRequest#CONTROL_MODE * @see CaptureRequest#CONTROL_SCENE_MODE * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL @@ -1156,7 +1157,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * is used, all non-zero weights will have the same effect. A region with 0 weight is * ignored.</p> * <p>If all regions have 0 weight, then no specific metering area needs to be used by the - * camera device.</p> + * camera device. The capture result will either be a zero weight region as well, or + * the region selected by the camera device as the focus area of interest.</p> * <p>If the metering region is outside the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in * capture result metadata, the camera device will ignore the sections outside the crop * region and output only the intersection rectangle as the metering region in the result @@ -2781,7 +2783,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * from the main sensor along the +X axis (to the right from the user's perspective) will * report <code>(0.03, 0, 0)</code>.</p> * <p>To transform a pixel coordinates between two cameras facing the same direction, first - * the source camera {@link CameraCharacteristics#LENS_RADIAL_DISTORTION android.lens.radialDistortion} must be corrected for. Then the source + * the source camera {@link CameraCharacteristics#LENS_DISTORTION android.lens.distortion} must be corrected for. Then the source * camera {@link CameraCharacteristics#LENS_INTRINSIC_CALIBRATION android.lens.intrinsicCalibration} needs to be applied, followed by the * {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation} of the source camera, the translation of the source camera * relative to the destination camera, the {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation} of the destination @@ -2795,10 +2797,10 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p><b>Units</b>: Meters</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * + * @see CameraCharacteristics#LENS_DISTORTION * @see CameraCharacteristics#LENS_INTRINSIC_CALIBRATION * @see CameraCharacteristics#LENS_POSE_REFERENCE * @see CameraCharacteristics#LENS_POSE_ROTATION - * @see CameraCharacteristics#LENS_RADIAL_DISTORTION */ @PublicKey public static final Key<float[]> LENS_POSE_TRANSLATION = @@ -2844,7 +2846,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * where <code>(0,0)</code> is the top-left of the * preCorrectionActiveArraySize rectangle. Once the pose and * intrinsic calibration transforms have been applied to a - * world point, then the {@link CameraCharacteristics#LENS_RADIAL_DISTORTION android.lens.radialDistortion} + * world point, then the {@link CameraCharacteristics#LENS_DISTORTION android.lens.distortion} * transform needs to be applied, and the result adjusted to * be in the {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} coordinate * system (where <code>(0, 0)</code> is the top-left of the @@ -2857,9 +2859,9 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * coordinate system.</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * + * @see CameraCharacteristics#LENS_DISTORTION * @see CameraCharacteristics#LENS_POSE_ROTATION * @see CameraCharacteristics#LENS_POSE_TRANSLATION - * @see CameraCharacteristics#LENS_RADIAL_DISTORTION * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE */ @@ -2901,12 +2903,59 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * * @see CameraCharacteristics#LENS_INTRINSIC_CALIBRATION + * @deprecated + * <p>This field was inconsistently defined in terms of its + * normalization. Use {@link CameraCharacteristics#LENS_DISTORTION android.lens.distortion} instead.</p> + * + * @see CameraCharacteristics#LENS_DISTORTION + */ + @Deprecated @PublicKey public static final Key<float[]> LENS_RADIAL_DISTORTION = new Key<float[]>("android.lens.radialDistortion", float[].class); /** + * <p>The correction coefficients to correct for this camera device's + * radial and tangential lens distortion.</p> + * <p>Replaces the deprecated {@link CameraCharacteristics#LENS_RADIAL_DISTORTION android.lens.radialDistortion} field, which was + * inconsistently defined.</p> + * <p>Three radial distortion coefficients <code>[kappa_1, kappa_2, + * kappa_3]</code> and two tangential distortion coefficients + * <code>[kappa_4, kappa_5]</code> that can be used to correct the + * lens's geometric distortion with the mapping equations:</p> + * <pre><code> x_c = x_i * ( 1 + kappa_1 * r^2 + kappa_2 * r^4 + kappa_3 * r^6 ) + + * kappa_4 * (2 * x_i * y_i) + kappa_5 * ( r^2 + 2 * x_i^2 ) + * y_c = y_i * ( 1 + kappa_1 * r^2 + kappa_2 * r^4 + kappa_3 * r^6 ) + + * kappa_5 * (2 * x_i * y_i) + kappa_4 * ( r^2 + 2 * y_i^2 ) + * </code></pre> + * <p>Here, <code>[x_c, y_c]</code> are the coordinates to sample in the + * input image that correspond to the pixel values in the + * corrected image at the coordinate <code>[x_i, y_i]</code>:</p> + * <pre><code> correctedImage(x_i, y_i) = sample_at(x_c, y_c, inputImage) + * </code></pre> + * <p>The pixel coordinates are defined in a coordinate system + * related to the {@link CameraCharacteristics#LENS_INTRINSIC_CALIBRATION android.lens.intrinsicCalibration} + * calibration fields; see that entry for details of the mapping stages. + * Both <code>[x_i, y_i]</code> and <code>[x_c, y_c]</code> + * have <code>(0,0)</code> at the lens optical center <code>[c_x, c_y]</code>, and + * the range of the coordinates depends on the focal length + * terms of the intrinsic calibration.</p> + * <p>Finally, <code>r</code> represents the radial distance from the + * optical center, <code>r^2 = x_i^2 + y_i^2</code>.</p> + * <p>The distortion model used is the Brown-Conrady model.</p> + * <p><b>Units</b>: + * Unitless coefficients.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CameraCharacteristics#LENS_INTRINSIC_CALIBRATION + * @see CameraCharacteristics#LENS_RADIAL_DISTORTION + */ + @PublicKey + public static final Key<float[]> LENS_DISTORTION = + new Key<float[]>("android.lens.distortion", float[].class); + + /** * <p>Mode of operation for the noise reduction algorithm.</p> * <p>The noise reduction algorithm attempts to improve image quality by removing * excessive noise added by the capture process, especially in dark conditions.</p> @@ -2979,6 +3028,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * Optional. Default value is FINAL.</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * @deprecated + * <p>Not used in HALv3 or newer</p> + * @hide */ @Deprecated @@ -2995,6 +3046,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * > 0</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * @deprecated + * <p>Not used in HALv3 or newer</p> + * @hide */ @Deprecated @@ -3775,6 +3828,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * * @see CaptureRequest#COLOR_CORRECTION_GAINS * @deprecated + * <p>Never fully implemented or specified; do not use</p> + * @hide */ @Deprecated @@ -3799,6 +3854,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * regardless of the android.control.* current values.</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * @deprecated + * <p>Never fully implemented or specified; do not use</p> + * @hide */ @Deprecated @@ -3909,21 +3966,18 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { new Key<Integer>("android.statistics.lensShadingMapMode", int.class); /** - * <p>Whether the camera device outputs the OIS data in output + * <p>A control for selecting whether OIS position information is included in output * result metadata.</p> - * <p>When set to ON, - * {@link CaptureResult#STATISTICS_OIS_TIMESTAMPS android.statistics.oisTimestamps}, android.statistics.oisShiftPixelX, - * android.statistics.oisShiftPixelY will provide OIS data in the output result metadata.</p> * <p><b>Possible values:</b> * <ul> * <li>{@link #STATISTICS_OIS_DATA_MODE_OFF OFF}</li> * <li>{@link #STATISTICS_OIS_DATA_MODE_ON ON}</li> * </ul></p> * <p><b>Available values for this device:</b><br> - * android.Statistics.info.availableOisDataModes</p> + * {@link CameraCharacteristics#STATISTICS_INFO_AVAILABLE_OIS_DATA_MODES android.statistics.info.availableOisDataModes}</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * - * @see CaptureResult#STATISTICS_OIS_TIMESTAMPS + * @see CameraCharacteristics#STATISTICS_INFO_AVAILABLE_OIS_DATA_MODES * @see #STATISTICS_OIS_DATA_MODE_OFF * @see #STATISTICS_OIS_DATA_MODE_ON */ @@ -3939,8 +3993,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * * @see CaptureResult#SENSOR_TIMESTAMP + * @hide */ - @PublicKey public static final Key<long[]> STATISTICS_OIS_TIMESTAMPS = new Key<long[]>("android.statistics.oisTimestamps", long[].class); @@ -3948,16 +4002,14 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>An array of shifts of OIS samples, in x direction.</p> * <p>The array contains the amount of shifts in x direction, in pixels, based on OIS samples. * A positive value is a shift from left to right in active array coordinate system. For - * example, if the optical center is (1000, 500) in active array coordinates, an shift of + * example, if the optical center is (1000, 500) in active array coordinates, a shift of * (3, 0) puts the new optical center at (1003, 500).</p> * <p>The number of shifts must match the number of timestamps in - * {@link CaptureResult#STATISTICS_OIS_TIMESTAMPS android.statistics.oisTimestamps}.</p> + * android.statistics.oisTimestamps.</p> * <p><b>Units</b>: Pixels in active array.</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> - * - * @see CaptureResult#STATISTICS_OIS_TIMESTAMPS + * @hide */ - @PublicKey public static final Key<float[]> STATISTICS_OIS_X_SHIFTS = new Key<float[]>("android.statistics.oisXShifts", float[].class); @@ -3965,20 +4017,35 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>An array of shifts of OIS samples, in y direction.</p> * <p>The array contains the amount of shifts in y direction, in pixels, based on OIS samples. * A positive value is a shift from top to bottom in active array coordinate system. For - * example, if the optical center is (1000, 500) in active array coordinates, an shift of + * example, if the optical center is (1000, 500) in active array coordinates, a shift of * (0, 5) puts the new optical center at (1000, 505).</p> * <p>The number of shifts must match the number of timestamps in - * {@link CaptureResult#STATISTICS_OIS_TIMESTAMPS android.statistics.oisTimestamps}.</p> + * android.statistics.oisTimestamps.</p> * <p><b>Units</b>: Pixels in active array.</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> - * - * @see CaptureResult#STATISTICS_OIS_TIMESTAMPS + * @hide */ - @PublicKey public static final Key<float[]> STATISTICS_OIS_Y_SHIFTS = new Key<float[]>("android.statistics.oisYShifts", float[].class); /** + * <p>An array of OIS samples.</p> + * <p>Each OIS sample contains the timestamp and the amount of shifts in x and y direction, + * in pixels, of the OIS sample.</p> + * <p>A positive value for a shift in x direction is a shift from left to right in active array + * coordinate system. For example, if the optical center is (1000, 500) in active array + * coordinates, a shift of (3, 0) puts the new optical center at (1003, 500).</p> + * <p>A positive value for a shift in y direction is a shift from top to bottom in active array + * coordinate system. For example, if the optical center is (1000, 500) in active array + * coordinates, a shift of (0, 5) puts the new optical center at (1000, 505).</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + */ + @PublicKey + @SyntheticKey + public static final Key<android.hardware.camera2.params.OisSample[]> STATISTICS_OIS_SAMPLES = + new Key<android.hardware.camera2.params.OisSample[]>("android.statistics.oisSamples", android.hardware.camera2.params.OisSample[].class); + + /** * <p>Tonemapping / contrast / gamma curve for the blue * channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is * CONTRAST_CURVE.</p> @@ -4029,6 +4096,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * of points can be less than max (that is, the request doesn't have to * always provide a curve with number of points equivalent to * {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}).</p> + * <p>For devices with MONOCHROME capability, only red channel is used. Green and blue channels + * are ignored.</p> * <p>A few examples, and their corresponding graphical mappings; these * only specify the red channel and the precision is limited to 4 * digits, for conciseness.</p> @@ -4091,6 +4160,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * of points can be less than max (that is, the request doesn't have to * always provide a curve with number of points equivalent to * {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}).</p> + * <p>For devices with MONOCHROME capability, only red channel is used. Green and blue channels + * are ignored.</p> * <p>A few examples, and their corresponding graphical mappings; these * only specify the red channel and the precision is limited to 4 * digits, for conciseness.</p> @@ -4383,6 +4454,49 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { public static final Key<Float> REPROCESS_EFFECTIVE_EXPOSURE_FACTOR = new Key<Float>("android.reprocess.effectiveExposureFactor", float.class); + /** + * <p>Mode of operation for the lens distortion correction block.</p> + * <p>The lens distortion correction block attempts to improve image quality by fixing + * radial, tangential, or other geometric aberrations in the camera device's optics. If + * available, the {@link CameraCharacteristics#LENS_DISTORTION android.lens.distortion} field documents the lens's distortion parameters.</p> + * <p>OFF means no distortion correction is done.</p> + * <p>FAST/HIGH_QUALITY both mean camera device determined distortion correction will be + * applied. HIGH_QUALITY mode indicates that the camera device will use the highest-quality + * correction algorithms, even if it slows down capture rate. FAST means the camera device + * will not slow down capture rate when applying correction. FAST may be the same as OFF if + * any correction at all would slow down capture rate. Every output stream will have a + * similar amount of enhancement applied.</p> + * <p>The correction only applies to processed outputs such as YUV, JPEG, or DEPTH16; it is not + * applied to any RAW output. Metadata coordinates such as face rectangles or metering + * regions are also not affected by correction.</p> + * <p>Applications enabling distortion correction need to pay extra attention when converting + * image coordinates between corrected output buffers and the sensor array. For example, if + * the app supports tap-to-focus and enables correction, it then has to apply the distortion + * model described in {@link CameraCharacteristics#LENS_DISTORTION android.lens.distortion} to the image buffer tap coordinates to properly + * calculate the tap position on the sensor active array to be used with + * {@link CaptureRequest#CONTROL_AF_REGIONS android.control.afRegions}. The same applies in reverse to detected face rectangles if + * they need to be drawn on top of the corrected output buffers.</p> + * <p><b>Possible values:</b> + * <ul> + * <li>{@link #DISTORTION_CORRECTION_MODE_OFF OFF}</li> + * <li>{@link #DISTORTION_CORRECTION_MODE_FAST FAST}</li> + * <li>{@link #DISTORTION_CORRECTION_MODE_HIGH_QUALITY HIGH_QUALITY}</li> + * </ul></p> + * <p><b>Available values for this device:</b><br> + * {@link CameraCharacteristics#DISTORTION_CORRECTION_AVAILABLE_MODES android.distortionCorrection.availableModes}</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CaptureRequest#CONTROL_AF_REGIONS + * @see CameraCharacteristics#DISTORTION_CORRECTION_AVAILABLE_MODES + * @see CameraCharacteristics#LENS_DISTORTION + * @see #DISTORTION_CORRECTION_MODE_OFF + * @see #DISTORTION_CORRECTION_MODE_FAST + * @see #DISTORTION_CORRECTION_MODE_HIGH_QUALITY + */ + @PublicKey + public static final Key<Integer> DISTORTION_CORRECTION_MODE = + new Key<Integer>("android.distortionCorrection.mode", int.class); + /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ * End generated code *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/ diff --git a/android/hardware/camera2/TotalCaptureResult.java b/android/hardware/camera2/TotalCaptureResult.java index 657745c8..4e20cb8d 100644 --- a/android/hardware/camera2/TotalCaptureResult.java +++ b/android/hardware/camera2/TotalCaptureResult.java @@ -19,10 +19,13 @@ package android.hardware.camera2; import android.annotation.NonNull; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.impl.CaptureResultExtras; +import android.hardware.camera2.impl.PhysicalCaptureResultInfo; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * <p>The total assembled results of a single image capture from the image sensor.</p> @@ -44,6 +47,12 @@ import java.util.List; * as {@link CaptureRequest#STATISTICS_FACE_DETECT_MODE}). Refer to each key documentation on * a case-by-case basis.</p> * + * <p>For a logical multi-camera device, if the CaptureRequest contains a surface for an underlying + * physical camera, the corresponding {@link TotalCaptureResult} object will include the metadata + * for that physical camera. And the mapping between the physical camera id and result metadata + * can be accessed via {@link #getPhysicalCameraResults}. If all requested surfaces are for the + * logical camera, no metadata for physical camera will be included.</p> + * * <p>{@link TotalCaptureResult} objects are immutable.</p> * * @see CameraDevice.CaptureCallback#onCaptureCompleted @@ -52,6 +61,8 @@ public final class TotalCaptureResult extends CaptureResult { private final List<CaptureResult> mPartialResults; private final int mSessionId; + // The map between physical camera id and capture result + private final HashMap<String, CaptureResult> mPhysicalCaptureResults; /** * Takes ownership of the passed-in camera metadata and the partial results @@ -60,7 +71,8 @@ public final class TotalCaptureResult extends CaptureResult { * @hide */ public TotalCaptureResult(CameraMetadataNative results, CaptureRequest parent, - CaptureResultExtras extras, List<CaptureResult> partials, int sessionId) { + CaptureResultExtras extras, List<CaptureResult> partials, int sessionId, + PhysicalCaptureResultInfo physicalResults[]) { super(results, parent, extras); if (partials == null) { @@ -70,6 +82,14 @@ public final class TotalCaptureResult extends CaptureResult { } mSessionId = sessionId; + + mPhysicalCaptureResults = new HashMap<String, CaptureResult>(); + for (PhysicalCaptureResultInfo onePhysicalResult : physicalResults) { + CaptureResult physicalResult = new CaptureResult( + onePhysicalResult.getCameraMetadata(), parent, extras); + mPhysicalCaptureResults.put(onePhysicalResult.getCameraId(), + physicalResult); + } } /** @@ -83,6 +103,7 @@ public final class TotalCaptureResult extends CaptureResult { mPartialResults = new ArrayList<>(); mSessionId = CameraCaptureSession.SESSION_ID_NONE; + mPhysicalCaptureResults = new HashMap<String, CaptureResult>(); } /** @@ -111,4 +132,22 @@ public final class TotalCaptureResult extends CaptureResult { public int getSessionId() { return mSessionId; } + + /** + * Get the map between physical camera ids and their capture result metadata + * + * <p>This function can be called for logical multi-camera devices, which are devices that have + * REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA capability and calls to {@link + * CameraCharacteristics#getPhysicalCameraIds} return a non-empty set of physical devices that + * are backing the logical camera.</p> + * + * <p>If one or more streams from the underlying physical cameras were requested by the + * corresponding capture request, this function returns the result metadata for those physical + * cameras. Otherwise, an empty map is returned.</p> + + * @return unmodifiable map between physical camera ids and their capture result metadata + */ + public Map<String, CaptureResult> getPhysicalCameraResults() { + return Collections.unmodifiableMap(mPhysicalCaptureResults); + } } diff --git a/android/hardware/camera2/dispatch/ArgumentReplacingDispatcher.java b/android/hardware/camera2/dispatch/ArgumentReplacingDispatcher.java deleted file mode 100644 index 866f370f..00000000 --- a/android/hardware/camera2/dispatch/ArgumentReplacingDispatcher.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.hardware.camera2.dispatch; - -import java.lang.reflect.Method; - -import static com.android.internal.util.Preconditions.*; - -/** - * A dispatcher that replaces one argument with another; replaces any argument at an index - * with another argument. - * - * <p>For example, we can override an {@code void onSomething(int x)} calls to have {@code x} always - * equal to 1. Or, if using this with a duck typing dispatcher, we could even overwrite {@code x} to - * be something - * that's not an {@code int}.</p> - * - * @param <T> - * source dispatch type, whose methods with {@link #dispatch} will be called - * @param <TArg> - * argument replacement type, args in {@link #dispatch} matching {@code argumentIndex} - * will be overriden to objects of this type - */ -public class ArgumentReplacingDispatcher<T, TArg> implements Dispatchable<T> { - - private final Dispatchable<T> mTarget; - private final int mArgumentIndex; - private final TArg mReplaceWith; - - /** - * Create a new argument replacing dispatcher; dispatches are forwarded to {@code target} - * after the argument is replaced. - * - * <p>For example, if a method {@code onAction(T1 a, Integer b, T2 c)} is invoked, and we wanted - * to replace all occurrences of {@code b} with {@code 0xDEADBEEF}, we would set - * {@code argumentIndex = 1} and {@code replaceWith = 0xDEADBEEF}.</p> - * - * <p>If a method dispatched has less arguments than {@code argumentIndex}, it is - * passed through with the arguments unchanged.</p> - * - * @param target destination dispatch type, methods will be redirected to this dispatcher - * @param argumentIndex the numeric index of the argument {@code >= 0} - * @param replaceWith arguments matching {@code argumentIndex} will be replaced with this object - */ - public ArgumentReplacingDispatcher(Dispatchable<T> target, int argumentIndex, - TArg replaceWith) { - mTarget = checkNotNull(target, "target must not be null"); - mArgumentIndex = checkArgumentNonnegative(argumentIndex, - "argumentIndex must not be negative"); - mReplaceWith = checkNotNull(replaceWith, "replaceWith must not be null"); - } - - @Override - public Object dispatch(Method method, Object[] args) throws Throwable { - - if (args.length > mArgumentIndex) { - args = arrayCopy(args); // don't change in-place since it can affect upstream dispatches - args[mArgumentIndex] = mReplaceWith; - } - - return mTarget.dispatch(method, args); - } - - private static Object[] arrayCopy(Object[] array) { - int length = array.length; - Object[] newArray = new Object[length]; - for (int i = 0; i < length; ++i) { - newArray[i] = array[i]; - } - return newArray; - } -} diff --git a/android/hardware/camera2/dispatch/BroadcastDispatcher.java b/android/hardware/camera2/dispatch/BroadcastDispatcher.java deleted file mode 100644 index fe575b27..00000000 --- a/android/hardware/camera2/dispatch/BroadcastDispatcher.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.hardware.camera2.dispatch; - - -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.List; - -import static com.android.internal.util.Preconditions.*; - -/** - * Broadcast a single dispatch into multiple other dispatchables. - * - * <p>Every time {@link #dispatch} is invoked, all the broadcast targets will - * see the same dispatch as well. The first target's return value is returned.</p> - * - * <p>This enables a single listener to be converted into a multi-listener.</p> - */ -public class BroadcastDispatcher<T> implements Dispatchable<T> { - - private final List<Dispatchable<T>> mDispatchTargets; - - /** - * Create a broadcast dispatcher from the supplied dispatch targets. - * - * @param dispatchTargets one or more targets to dispatch to - */ - @SafeVarargs - public BroadcastDispatcher(Dispatchable<T>... dispatchTargets) { - mDispatchTargets = Arrays.asList( - checkNotNull(dispatchTargets, "dispatchTargets must not be null")); - } - - @Override - public Object dispatch(Method method, Object[] args) throws Throwable { - Object result = null; - boolean gotResult = false; - - for (Dispatchable<T> dispatchTarget : mDispatchTargets) { - Object localResult = dispatchTarget.dispatch(method, args); - - if (!gotResult) { - gotResult = true; - result = localResult; - } - } - - return result; - } -} diff --git a/android/hardware/camera2/dispatch/Dispatchable.java b/android/hardware/camera2/dispatch/Dispatchable.java deleted file mode 100644 index 753103f4..00000000 --- a/android/hardware/camera2/dispatch/Dispatchable.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.hardware.camera2.dispatch; - -import java.lang.reflect.Method; - -/** - * Dynamically dispatch a method and its argument to some object. - * - * <p>This can be used to intercept method calls and do work around them, redirect work, - * or block calls entirely.</p> - */ -public interface Dispatchable<T> { - /** - * Dispatch the method and arguments to this object. - * @param method a method defined in class {@code T} - * @param args arguments corresponding to said {@code method} - * @return the object returned when invoking {@code method} - * @throws Throwable any exception that might have been raised while invoking the method - */ - public Object dispatch(Method method, Object[] args) throws Throwable; -} diff --git a/android/hardware/camera2/dispatch/DuckTypingDispatcher.java b/android/hardware/camera2/dispatch/DuckTypingDispatcher.java deleted file mode 100644 index 75f97e46..00000000 --- a/android/hardware/camera2/dispatch/DuckTypingDispatcher.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.hardware.camera2.dispatch; - - -import java.lang.reflect.Method; - -import static com.android.internal.util.Preconditions.*; - -/** - * Duck typing dispatcher; converts dispatch methods calls from one class to another by - * looking up equivalently methods at runtime by name. - * - * <p>For example, if two types have identical method names and arguments, but - * are not subclasses/subinterfaces of each other, this dispatcher will allow calls to be - * made from one type to the other.</p> - * - * @param <TFrom> source dispatch type, whose methods with {@link #dispatch} will be called - * @param <T> destination dispatch type, methods will be converted to the class of {@code T} - */ -public class DuckTypingDispatcher<TFrom, T> implements Dispatchable<TFrom> { - - private final MethodNameInvoker<T> mDuck; - - /** - * Create a new duck typing dispatcher. - * - * @param target destination dispatch type, methods will be redirected to this dispatcher - * @param targetClass destination dispatch class, methods will be converted to this class's - */ - public DuckTypingDispatcher(Dispatchable<T> target, Class<T> targetClass) { - checkNotNull(targetClass, "targetClass must not be null"); - checkNotNull(target, "target must not be null"); - - mDuck = new MethodNameInvoker<T>(target, targetClass); - } - - @Override - public Object dispatch(Method method, Object[] args) { - return mDuck.invoke(method.getName(), args); - } -} diff --git a/android/hardware/camera2/dispatch/HandlerDispatcher.java b/android/hardware/camera2/dispatch/HandlerDispatcher.java deleted file mode 100644 index f8e9d49a..00000000 --- a/android/hardware/camera2/dispatch/HandlerDispatcher.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.hardware.camera2.dispatch; - -import android.hardware.camera2.utils.UncheckedThrow; -import android.os.Handler; -import android.util.Log; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -import static com.android.internal.util.Preconditions.*; - -/** - * Forward all interface calls into a handler by posting it as a {@code Runnable}. - * - * <p>All calls will return immediately; functions with return values will return a default - * value of {@code null}, {@code 0}, or {@code false} where that value is legal.</p> - * - * <p>Any exceptions thrown on the handler while trying to invoke a method - * will be re-thrown. Throwing checked exceptions on a handler which doesn't expect any - * checked exceptions to be thrown will result in "undefined" behavior - * (although in practice it is usually thrown as normal).</p> - */ -public class HandlerDispatcher<T> implements Dispatchable<T> { - - private static final String TAG = "HandlerDispatcher"; - - private final Dispatchable<T> mDispatchTarget; - private final Handler mHandler; - - /** - * Create a dispatcher that forwards it's dispatch calls by posting - * them onto the {@code handler} as a {@code Runnable}. - * - * @param dispatchTarget the destination whose method calls will be redirected into the handler - * @param handler all calls into {@code dispatchTarget} will be posted onto this handler - * @param <T> the type of the element you want to wrap. - * @return a dispatcher that will forward it's dispatch calls to a handler - */ - public HandlerDispatcher(Dispatchable<T> dispatchTarget, Handler handler) { - mDispatchTarget = checkNotNull(dispatchTarget, "dispatchTarget must not be null"); - mHandler = checkNotNull(handler, "handler must not be null"); - } - - @Override - public Object dispatch(final Method method, final Object[] args) throws Throwable { - mHandler.post(new Runnable() { - @Override - public void run() { - try { - mDispatchTarget.dispatch(method, args); - } catch (InvocationTargetException e) { - Throwable t = e.getTargetException(); - // Potential UB. Hopefully 't' is a runtime exception. - UncheckedThrow.throwAnyException(t); - } catch (IllegalAccessException e) { - // Impossible - Log.wtf(TAG, "IllegalAccessException while invoking " + method, e); - } catch (IllegalArgumentException e) { - // Impossible - Log.wtf(TAG, "IllegalArgumentException while invoking " + method, e); - } catch (Throwable e) { - UncheckedThrow.throwAnyException(e); - } - } - }); - - // TODO handle primitive return values that would avoid NPE if unboxed - return null; - } -} diff --git a/android/hardware/camera2/dispatch/InvokeDispatcher.java b/android/hardware/camera2/dispatch/InvokeDispatcher.java deleted file mode 100644 index ac5f5267..00000000 --- a/android/hardware/camera2/dispatch/InvokeDispatcher.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.hardware.camera2.dispatch; - -import android.hardware.camera2.utils.UncheckedThrow; -import android.util.Log; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -import static com.android.internal.util.Preconditions.*; - - -public class InvokeDispatcher<T> implements Dispatchable<T> { - - private static final String TAG = "InvocationSink"; - private final T mTarget; - - public InvokeDispatcher(T target) { - mTarget = checkNotNull(target, "target must not be null"); - } - - @Override - public Object dispatch(Method method, Object[] args) { - try { - return method.invoke(mTarget, args); - } catch (InvocationTargetException e) { - Throwable t = e.getTargetException(); - // Potential UB. Hopefully 't' is a runtime exception. - UncheckedThrow.throwAnyException(t); - } catch (IllegalAccessException e) { - // Impossible - Log.wtf(TAG, "IllegalAccessException while invoking " + method, e); - } catch (IllegalArgumentException e) { - // Impossible - Log.wtf(TAG, "IllegalArgumentException while invoking " + method, e); - } - - // unreachable - return null; - } -} diff --git a/android/hardware/camera2/dispatch/MethodNameInvoker.java b/android/hardware/camera2/dispatch/MethodNameInvoker.java deleted file mode 100644 index 8296b7a9..00000000 --- a/android/hardware/camera2/dispatch/MethodNameInvoker.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.hardware.camera2.dispatch; - -import static com.android.internal.util.Preconditions.checkNotNull; - -import android.hardware.camera2.utils.UncheckedThrow; - -import java.lang.reflect.Method; -import java.util.concurrent.ConcurrentHashMap; - -/** - * Invoke a method on a dispatchable by its name (without knowing the {@code Method} ahead of time). - * - * @param <T> destination dispatch type, methods will be looked up in the class of {@code T} - */ -public class MethodNameInvoker<T> { - - private final Dispatchable<T> mTarget; - private final Class<T> mTargetClass; - private final Method[] mTargetClassMethods; - private final ConcurrentHashMap<String, Method> mMethods = - new ConcurrentHashMap<>(); - - /** - * Create a new method name invoker. - * - * @param target destination dispatch type, invokes will be redirected to this dispatcher - * @param targetClass destination dispatch class, the invoked methods will be from this class - */ - public MethodNameInvoker(Dispatchable<T> target, Class<T> targetClass) { - mTargetClass = targetClass; - mTargetClassMethods = targetClass.getMethods(); - mTarget = target; - } - - /** - * Invoke a method by its name. - * - * <p>If more than one method exists in {@code targetClass}, the first method with the right - * number of arguments will be used, and later calls will all use that method.</p> - * - * @param methodName - * The name of the method, which will be matched 1:1 to the destination method - * @param params - * Variadic parameter list. - * @return - * The same kind of value that would normally be returned by calling {@code methodName} - * statically. - * - * @throws IllegalArgumentException if {@code methodName} does not exist on the target class - * @throws Throwable will rethrow anything that the target method would normally throw - */ - @SuppressWarnings("unchecked") - public <K> K invoke(String methodName, Object... params) { - checkNotNull(methodName, "methodName must not be null"); - - Method targetMethod = mMethods.get(methodName); - if (targetMethod == null) { - for (Method method : mTargetClassMethods) { - // TODO future: match types of params if possible - if (method.getName().equals(methodName) && - (params.length == method.getParameterTypes().length) ) { - targetMethod = method; - mMethods.put(methodName, targetMethod); - break; - } - } - - if (targetMethod == null) { - throw new IllegalArgumentException( - "Method " + methodName + " does not exist on class " + mTargetClass); - } - } - - try { - return (K) mTarget.dispatch(targetMethod, params); - } catch (Throwable e) { - UncheckedThrow.throwAnyException(e); - // unreachable - return null; - } - } -} diff --git a/android/hardware/camera2/dispatch/NullDispatcher.java b/android/hardware/camera2/dispatch/NullDispatcher.java deleted file mode 100644 index fada075c..00000000 --- a/android/hardware/camera2/dispatch/NullDispatcher.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.hardware.camera2.dispatch; - - -import java.lang.reflect.Method; - -/** - * Do nothing when dispatching; follows the null object pattern. - */ -public class NullDispatcher<T> implements Dispatchable<T> { - /** - * Create a dispatcher that does nothing when dispatched to. - */ - public NullDispatcher() { - } - - /** - * Do nothing; all parameters are ignored. - */ - @Override - public Object dispatch(Method method, Object[] args) { - return null; - } -} diff --git a/android/hardware/camera2/impl/CallbackProxies.java b/android/hardware/camera2/impl/CallbackProxies.java index c9eecf10..9e4cb80b 100644 --- a/android/hardware/camera2/impl/CallbackProxies.java +++ b/android/hardware/camera2/impl/CallbackProxies.java @@ -15,16 +15,17 @@ */ package android.hardware.camera2.impl; +import android.os.Binder; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CaptureFailure; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureResult; import android.hardware.camera2.TotalCaptureResult; -import android.hardware.camera2.dispatch.Dispatchable; -import android.hardware.camera2.dispatch.MethodNameInvoker; import android.view.Surface; +import java.util.concurrent.Executor; + import static com.android.internal.util.Preconditions.*; /** @@ -34,164 +35,86 @@ import static com.android.internal.util.Preconditions.*; * to use our own proxy mechanism.</p> */ public class CallbackProxies { - - // TODO: replace with codegen - - public static class DeviceStateCallbackProxy extends CameraDeviceImpl.StateCallbackKK { - private final MethodNameInvoker<CameraDeviceImpl.StateCallbackKK> mProxy; - - public DeviceStateCallbackProxy( - Dispatchable<CameraDeviceImpl.StateCallbackKK> dispatchTarget) { - dispatchTarget = checkNotNull(dispatchTarget, "dispatchTarget must not be null"); - mProxy = new MethodNameInvoker<>(dispatchTarget, CameraDeviceImpl.StateCallbackKK.class); - } - - @Override - public void onOpened(CameraDevice camera) { - mProxy.invoke("onOpened", camera); - } - - @Override - public void onDisconnected(CameraDevice camera) { - mProxy.invoke("onDisconnected", camera); - } - - @Override - public void onError(CameraDevice camera, int error) { - mProxy.invoke("onError", camera, error); - } - - @Override - public void onUnconfigured(CameraDevice camera) { - mProxy.invoke("onUnconfigured", camera); - } - - @Override - public void onActive(CameraDevice camera) { - mProxy.invoke("onActive", camera); - } - - @Override - public void onBusy(CameraDevice camera) { - mProxy.invoke("onBusy", camera); - } - - @Override - public void onClosed(CameraDevice camera) { - mProxy.invoke("onClosed", camera); - } - - @Override - public void onIdle(CameraDevice camera) { - mProxy.invoke("onIdle", camera); - } - } - - @SuppressWarnings("deprecation") - public static class DeviceCaptureCallbackProxy implements CameraDeviceImpl.CaptureCallback { - private final MethodNameInvoker<CameraDeviceImpl.CaptureCallback> mProxy; - - public DeviceCaptureCallbackProxy( - Dispatchable<CameraDeviceImpl.CaptureCallback> dispatchTarget) { - dispatchTarget = checkNotNull(dispatchTarget, "dispatchTarget must not be null"); - mProxy = new MethodNameInvoker<>(dispatchTarget, CameraDeviceImpl.CaptureCallback.class); - } - - @Override - public void onCaptureStarted(CameraDevice camera, - CaptureRequest request, long timestamp, long frameNumber) { - mProxy.invoke("onCaptureStarted", camera, request, timestamp, frameNumber); - } - - @Override - public void onCapturePartial(CameraDevice camera, - CaptureRequest request, CaptureResult result) { - mProxy.invoke("onCapturePartial", camera, request, result); - } - - @Override - public void onCaptureProgressed(CameraDevice camera, - CaptureRequest request, CaptureResult partialResult) { - mProxy.invoke("onCaptureProgressed", camera, request, partialResult); - } - - @Override - public void onCaptureCompleted(CameraDevice camera, - CaptureRequest request, TotalCaptureResult result) { - mProxy.invoke("onCaptureCompleted", camera, request, result); - } - - @Override - public void onCaptureFailed(CameraDevice camera, - CaptureRequest request, CaptureFailure failure) { - mProxy.invoke("onCaptureFailed", camera, request, failure); - } - - @Override - public void onCaptureSequenceCompleted(CameraDevice camera, - int sequenceId, long frameNumber) { - mProxy.invoke("onCaptureSequenceCompleted", camera, sequenceId, frameNumber); - } - - @Override - public void onCaptureSequenceAborted(CameraDevice camera, - int sequenceId) { - mProxy.invoke("onCaptureSequenceAborted", camera, sequenceId); - } - - @Override - public void onCaptureBufferLost(CameraDevice camera, - CaptureRequest request, Surface target, long frameNumber) { - mProxy.invoke("onCaptureBufferLost", camera, request, target, frameNumber); - } - - } - public static class SessionStateCallbackProxy extends CameraCaptureSession.StateCallback { - private final MethodNameInvoker<CameraCaptureSession.StateCallback> mProxy; + private final Executor mExecutor; + private final CameraCaptureSession.StateCallback mCallback; - public SessionStateCallbackProxy( - Dispatchable<CameraCaptureSession.StateCallback> dispatchTarget) { - dispatchTarget = checkNotNull(dispatchTarget, "dispatchTarget must not be null"); - mProxy = new MethodNameInvoker<>(dispatchTarget, - CameraCaptureSession.StateCallback.class); + public SessionStateCallbackProxy(Executor executor, + CameraCaptureSession.StateCallback callback) { + mExecutor = checkNotNull(executor, "executor must not be null"); + mCallback = checkNotNull(callback, "callback must not be null"); } @Override public void onConfigured(CameraCaptureSession session) { - mProxy.invoke("onConfigured", session); + final long ident = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onConfigured(session)); + } finally { + Binder.restoreCallingIdentity(ident); + } } @Override public void onConfigureFailed(CameraCaptureSession session) { - mProxy.invoke("onConfigureFailed", session); + final long ident = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onConfigureFailed(session)); + } finally { + Binder.restoreCallingIdentity(ident); + } } @Override public void onReady(CameraCaptureSession session) { - mProxy.invoke("onReady", session); + final long ident = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onReady(session)); + } finally { + Binder.restoreCallingIdentity(ident); + } } @Override public void onActive(CameraCaptureSession session) { - mProxy.invoke("onActive", session); + final long ident = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onActive(session)); + } finally { + Binder.restoreCallingIdentity(ident); + } } @Override public void onCaptureQueueEmpty(CameraCaptureSession session) { - mProxy.invoke("onCaptureQueueEmpty", session); + final long ident = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onCaptureQueueEmpty(session)); + } finally { + Binder.restoreCallingIdentity(ident); + } } @Override public void onClosed(CameraCaptureSession session) { - mProxy.invoke("onClosed", session); + final long ident = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onClosed(session)); + } finally { + Binder.restoreCallingIdentity(ident); + } } @Override public void onSurfacePrepared(CameraCaptureSession session, Surface surface) { - mProxy.invoke("onSurfacePrepared", session, surface); + final long ident = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onSurfacePrepared(session, surface)); + } finally { + Binder.restoreCallingIdentity(ident); + } } } diff --git a/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/android/hardware/camera2/impl/CameraCaptureSessionImpl.java index 8b8bbc34..a4640c1f 100644 --- a/android/hardware/camera2/impl/CameraCaptureSessionImpl.java +++ b/android/hardware/camera2/impl/CameraCaptureSessionImpl.java @@ -20,20 +20,17 @@ import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.ICameraDeviceUser; -import android.hardware.camera2.dispatch.ArgumentReplacingDispatcher; -import android.hardware.camera2.dispatch.BroadcastDispatcher; -import android.hardware.camera2.dispatch.DuckTypingDispatcher; -import android.hardware.camera2.dispatch.HandlerDispatcher; -import android.hardware.camera2.dispatch.InvokeDispatcher; import android.hardware.camera2.params.OutputConfiguration; import android.hardware.camera2.utils.TaskDrainer; import android.hardware.camera2.utils.TaskSingleDrainer; +import android.os.Binder; import android.os.Handler; import android.util.Log; import android.view.Surface; import java.util.Arrays; import java.util.List; +import java.util.concurrent.Executor; import static android.hardware.camera2.impl.CameraDeviceImpl.checkHandler; import static com.android.internal.util.Preconditions.*; @@ -51,16 +48,16 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession private final Surface mInput; /** * User-specified state callback, used for outgoing events; calls to this object will be - * automatically {@link Handler#post(Runnable) posted} to {@code mStateHandler}. + * automatically invoked via {@code mStateExecutor}. */ private final CameraCaptureSession.StateCallback mStateCallback; - /** User-specified state handler used for outgoing state callback events */ - private final Handler mStateHandler; + /** User-specified state executor used for outgoing state callback events */ + private final Executor mStateExecutor; /** Internal camera device; used to translate calls into existing deprecated API */ private final android.hardware.camera2.impl.CameraDeviceImpl mDeviceImpl; - /** Internal handler; used for all incoming events to preserve total order */ - private final Handler mDeviceHandler; + /** Internal executor; used for all incoming events to preserve total order */ + private final Executor mDeviceExecutor; /** Drain Sequence IDs which have been queued but not yet finished with aborted/completed */ private final TaskDrainer<Integer> mSequenceDrainer; @@ -87,9 +84,9 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession * (e.g. no pending captures, no repeating requests, no flush).</p> */ CameraCaptureSessionImpl(int id, Surface input, - CameraCaptureSession.StateCallback callback, Handler stateHandler, + CameraCaptureSession.StateCallback callback, Executor stateExecutor, android.hardware.camera2.impl.CameraDeviceImpl deviceImpl, - Handler deviceStateHandler, boolean configureSuccess) { + Executor deviceStateExecutor, boolean configureSuccess) { if (callback == null) { throw new IllegalArgumentException("callback must not be null"); } @@ -98,10 +95,11 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession mIdString = String.format("Session %d: ", mId); mInput = input; - mStateHandler = checkHandler(stateHandler); - mStateCallback = createUserStateCallbackProxy(mStateHandler, callback); + mStateExecutor = checkNotNull(stateExecutor, "stateExecutor must not be null"); + mStateCallback = createUserStateCallbackProxy(mStateExecutor, callback); - mDeviceHandler = checkNotNull(deviceStateHandler, "deviceStateHandler must not be null"); + mDeviceExecutor = checkNotNull(deviceStateExecutor, + "deviceStateExecutor must not be null"); mDeviceImpl = checkNotNull(deviceImpl, "deviceImpl must not be null"); /* @@ -110,11 +108,11 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession * This ensures total ordering between CameraDevice.StateCallback and * CameraDeviceImpl.CaptureCallback events. */ - mSequenceDrainer = new TaskDrainer<>(mDeviceHandler, new SequenceDrainListener(), + mSequenceDrainer = new TaskDrainer<>(mDeviceExecutor, new SequenceDrainListener(), /*name*/"seq"); - mIdleDrainer = new TaskSingleDrainer(mDeviceHandler, new IdleDrainListener(), + mIdleDrainer = new TaskSingleDrainer(mDeviceExecutor, new IdleDrainListener(), /*name*/"idle"); - mAbortDrainer = new TaskSingleDrainer(mDeviceHandler, new AbortDrainListener(), + mAbortDrainer = new TaskSingleDrainer(mDeviceExecutor, new AbortDrainListener(), /*name*/"abort"); // CameraDevice should call configureOutputs and have it finish before constructing us @@ -160,6 +158,49 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession @Override public int capture(CaptureRequest request, CaptureCallback callback, Handler handler) throws CameraAccessException { + checkCaptureRequest(request); + + synchronized (mDeviceImpl.mInterfaceLock) { + checkNotClosed(); + + handler = checkHandler(handler, callback); + + if (DEBUG) { + Log.v(TAG, mIdString + "capture - request " + request + ", callback " + callback + + " handler " + handler); + } + + return addPendingSequence(mDeviceImpl.capture(request, + createCaptureCallbackProxy(handler, callback), mDeviceExecutor)); + } + } + + @Override + public int captureSingleRequest(CaptureRequest request, Executor executor, + CaptureCallback callback) throws CameraAccessException { + if (executor == null) { + throw new IllegalArgumentException("executor must not be null"); + } else if (callback == null) { + throw new IllegalArgumentException("callback must not be null"); + } + checkCaptureRequest(request); + + synchronized (mDeviceImpl.mInterfaceLock) { + checkNotClosed(); + + executor = CameraDeviceImpl.checkExecutor(executor, callback); + + if (DEBUG) { + Log.v(TAG, mIdString + "capture - request " + request + ", callback " + callback + + " executor " + executor); + } + + return addPendingSequence(mDeviceImpl.capture(request, + createCaptureCallbackProxyWithExecutor(executor, callback), mDeviceExecutor)); + } + } + + private void checkCaptureRequest(CaptureRequest request) { if (request == null) { throw new IllegalArgumentException("request must not be null"); } else if (request.isReprocess() && !isReprocessable()) { @@ -168,6 +209,12 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession } else if (request.isReprocess() && request.getReprocessableSessionId() != mId) { throw new IllegalArgumentException("capture request was created for another session"); } + } + + @Override + public int captureBurst(List<CaptureRequest> requests, CaptureCallback callback, + Handler handler) throws CameraAccessException { + checkCaptureRequests(requests); synchronized (mDeviceImpl.mInterfaceLock) { checkNotClosed(); @@ -175,18 +222,43 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession handler = checkHandler(handler, callback); if (DEBUG) { - Log.v(TAG, mIdString + "capture - request " + request + ", callback " + callback + - " handler " + handler); + CaptureRequest[] requestArray = requests.toArray(new CaptureRequest[0]); + Log.v(TAG, mIdString + "captureBurst - requests " + Arrays.toString(requestArray) + + ", callback " + callback + " handler " + handler); } - return addPendingSequence(mDeviceImpl.capture(request, - createCaptureCallbackProxy(handler, callback), mDeviceHandler)); + return addPendingSequence(mDeviceImpl.captureBurst(requests, + createCaptureCallbackProxy(handler, callback), mDeviceExecutor)); } } @Override - public int captureBurst(List<CaptureRequest> requests, CaptureCallback callback, - Handler handler) throws CameraAccessException { + public int captureBurstRequests(List<CaptureRequest> requests, Executor executor, + CaptureCallback callback) throws CameraAccessException { + if (executor == null) { + throw new IllegalArgumentException("executor must not be null"); + } else if (callback == null) { + throw new IllegalArgumentException("callback must not be null"); + } + checkCaptureRequests(requests); + + synchronized (mDeviceImpl.mInterfaceLock) { + checkNotClosed(); + + executor = CameraDeviceImpl.checkExecutor(executor, callback); + + if (DEBUG) { + CaptureRequest[] requestArray = requests.toArray(new CaptureRequest[0]); + Log.v(TAG, mIdString + "captureBurst - requests " + Arrays.toString(requestArray) + + ", callback " + callback + " executor " + executor); + } + + return addPendingSequence(mDeviceImpl.captureBurst(requests, + createCaptureCallbackProxyWithExecutor(executor, callback), mDeviceExecutor)); + } + } + + private void checkCaptureRequests(List<CaptureRequest> requests) { if (requests == null) { throw new IllegalArgumentException("Requests must not be null"); } else if (requests.isEmpty()) { @@ -205,76 +277,122 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession } } + } + + @Override + public int setRepeatingRequest(CaptureRequest request, CaptureCallback callback, + Handler handler) throws CameraAccessException { + checkRepeatingRequest(request); + synchronized (mDeviceImpl.mInterfaceLock) { checkNotClosed(); handler = checkHandler(handler, callback); if (DEBUG) { - CaptureRequest[] requestArray = requests.toArray(new CaptureRequest[0]); - Log.v(TAG, mIdString + "captureBurst - requests " + Arrays.toString(requestArray) + - ", callback " + callback + " handler " + handler); + Log.v(TAG, mIdString + "setRepeatingRequest - request " + request + ", callback " + + callback + " handler" + " " + handler); } - return addPendingSequence(mDeviceImpl.captureBurst(requests, - createCaptureCallbackProxy(handler, callback), mDeviceHandler)); + return addPendingSequence(mDeviceImpl.setRepeatingRequest(request, + createCaptureCallbackProxy(handler, callback), mDeviceExecutor)); } } @Override - public int setRepeatingRequest(CaptureRequest request, CaptureCallback callback, - 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"); + public int setSingleRepeatingRequest(CaptureRequest request, Executor executor, + CaptureCallback callback) throws CameraAccessException { + if (executor == null) { + throw new IllegalArgumentException("executor must not be null"); + } else if (callback == null) { + throw new IllegalArgumentException("callback must not be null"); } + checkRepeatingRequest(request); synchronized (mDeviceImpl.mInterfaceLock) { checkNotClosed(); - handler = checkHandler(handler, callback); + executor = CameraDeviceImpl.checkExecutor(executor, callback); if (DEBUG) { Log.v(TAG, mIdString + "setRepeatingRequest - request " + request + ", callback " + - callback + " handler" + " " + handler); + callback + " executor" + " " + executor); } return addPendingSequence(mDeviceImpl.setRepeatingRequest(request, - createCaptureCallbackProxy(handler, callback), mDeviceHandler)); + createCaptureCallbackProxyWithExecutor(executor, callback), mDeviceExecutor)); + } + } + + private void checkRepeatingRequest(CaptureRequest request) { + if (request == null) { + throw new IllegalArgumentException("request must not be null"); + } else if (request.isReprocess()) { + throw new IllegalArgumentException("repeating reprocess requests are not supported"); } } @Override public int setRepeatingBurst(List<CaptureRequest> requests, CaptureCallback callback, Handler handler) throws CameraAccessException { - if (requests == null) { - throw new IllegalArgumentException("requests must not be null"); - } else if (requests.isEmpty()) { - throw new IllegalArgumentException("requests must have at least one element"); - } + checkRepeatingRequests(requests); - for (CaptureRequest r : requests) { - if (r.isReprocess()) { - throw new IllegalArgumentException("repeating reprocess burst requests are not " + - "supported"); + synchronized (mDeviceImpl.mInterfaceLock) { + checkNotClosed(); + + handler = checkHandler(handler, callback); + + if (DEBUG) { + CaptureRequest[] requestArray = requests.toArray(new CaptureRequest[0]); + Log.v(TAG, mIdString + "setRepeatingBurst - requests " + + Arrays.toString(requestArray) + ", callback " + callback + + " handler" + "" + handler); } + + return addPendingSequence(mDeviceImpl.setRepeatingBurst(requests, + createCaptureCallbackProxy(handler, callback), mDeviceExecutor)); + } + } + + @Override + public int setRepeatingBurstRequests(List<CaptureRequest> requests, Executor executor, + CaptureCallback callback) throws CameraAccessException { + if (executor == null) { + throw new IllegalArgumentException("executor must not be null"); + } else if (callback == null) { + throw new IllegalArgumentException("callback must not be null"); } + checkRepeatingRequests(requests); synchronized (mDeviceImpl.mInterfaceLock) { checkNotClosed(); - handler = checkHandler(handler, callback); + executor = CameraDeviceImpl.checkExecutor(executor, callback); if (DEBUG) { CaptureRequest[] requestArray = requests.toArray(new CaptureRequest[0]); Log.v(TAG, mIdString + "setRepeatingBurst - requests " + Arrays.toString(requestArray) + ", callback " + callback + - " handler" + "" + handler); + " executor" + "" + executor); } return addPendingSequence(mDeviceImpl.setRepeatingBurst(requests, - createCaptureCallbackProxy(handler, callback), mDeviceHandler)); + createCaptureCallbackProxyWithExecutor(executor, callback), mDeviceExecutor)); + } + } + + private void checkRepeatingRequests(List<CaptureRequest> requests) { + if (requests == null) { + throw new IllegalArgumentException("requests must not be null"); + } else if (requests.isEmpty()) { + 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"); + } } } @@ -446,119 +564,150 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession } /** - * Post calls into a CameraCaptureSession.StateCallback to the user-specified {@code handler}. + * Post calls into a CameraCaptureSession.StateCallback to the user-specified {@code executor}. */ - private StateCallback createUserStateCallbackProxy(Handler handler, StateCallback callback) { - InvokeDispatcher<StateCallback> userCallbackSink = new InvokeDispatcher<>(callback); - HandlerDispatcher<StateCallback> handlerPassthrough = - new HandlerDispatcher<>(userCallbackSink, handler); - - return new CallbackProxies.SessionStateCallbackProxy(handlerPassthrough); + private StateCallback createUserStateCallbackProxy(Executor executor, StateCallback callback) { + return new CallbackProxies.SessionStateCallbackProxy(executor, callback); } /** * Forward callbacks from * CameraDeviceImpl.CaptureCallback to the CameraCaptureSession.CaptureCallback. * - * <p>In particular, all calls are automatically split to go both to our own - * internal callback, and to the user-specified callback (by transparently posting - * to the user-specified handler).</p> - * * <p>When a capture sequence finishes, update the pending checked sequences set.</p> */ @SuppressWarnings("deprecation") private CameraDeviceImpl.CaptureCallback createCaptureCallbackProxy( Handler handler, CaptureCallback callback) { - CameraDeviceImpl.CaptureCallback localCallback = new CameraDeviceImpl.CaptureCallback() { + final Executor executor = (callback != null) ? CameraDeviceImpl.checkAndWrapHandler( + handler) : null; + return createCaptureCallbackProxyWithExecutor(executor, callback); + } + + private CameraDeviceImpl.CaptureCallback createCaptureCallbackProxyWithExecutor( + Executor executor, CaptureCallback callback) { + return new CameraDeviceImpl.CaptureCallback() { @Override public void onCaptureStarted(CameraDevice camera, CaptureRequest request, long timestamp, long frameNumber) { - // Do nothing + if ((callback != null) && (executor != null)) { + final long ident = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onCaptureStarted( + CameraCaptureSessionImpl.this, request, timestamp, + frameNumber)); + } finally { + Binder.restoreCallingIdentity(ident); + } + } } @Override public void onCapturePartial(CameraDevice camera, CaptureRequest request, android.hardware.camera2.CaptureResult result) { - // Do nothing + if ((callback != null) && (executor != null)) { + final long ident = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onCapturePartial( + CameraCaptureSessionImpl.this, request, result)); + } finally { + Binder.restoreCallingIdentity(ident); + } + } } @Override public void onCaptureProgressed(CameraDevice camera, CaptureRequest request, android.hardware.camera2.CaptureResult partialResult) { - // Do nothing + if ((callback != null) && (executor != null)) { + final long ident = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onCaptureProgressed( + CameraCaptureSessionImpl.this, request, partialResult)); + } finally { + Binder.restoreCallingIdentity(ident); + } + } } @Override public void onCaptureCompleted(CameraDevice camera, CaptureRequest request, android.hardware.camera2.TotalCaptureResult result) { - // Do nothing + if ((callback != null) && (executor != null)) { + final long ident = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onCaptureCompleted( + CameraCaptureSessionImpl.this, request, result)); + } finally { + Binder.restoreCallingIdentity(ident); + } + } } @Override public void onCaptureFailed(CameraDevice camera, CaptureRequest request, android.hardware.camera2.CaptureFailure failure) { - // Do nothing + if ((callback != null) && (executor != null)) { + final long ident = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onCaptureFailed( + CameraCaptureSessionImpl.this, request, failure)); + } finally { + Binder.restoreCallingIdentity(ident); + } + } } @Override public void onCaptureSequenceCompleted(CameraDevice camera, int sequenceId, long frameNumber) { + if ((callback != null) && (executor != null)) { + final long ident = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onCaptureSequenceCompleted( + CameraCaptureSessionImpl.this, sequenceId, frameNumber)); + } finally { + Binder.restoreCallingIdentity(ident); + } + } finishPendingSequence(sequenceId); } @Override public void onCaptureSequenceAborted(CameraDevice camera, int sequenceId) { + if ((callback != null) && (executor != null)) { + final long ident = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onCaptureSequenceAborted( + CameraCaptureSessionImpl.this, sequenceId)); + } finally { + Binder.restoreCallingIdentity(ident); + } + } finishPendingSequence(sequenceId); } @Override public void onCaptureBufferLost(CameraDevice camera, CaptureRequest request, Surface target, long frameNumber) { - // Do nothing + if ((callback != null) && (executor != null)) { + final long ident = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onCaptureBufferLost( + CameraCaptureSessionImpl.this, request, target, frameNumber)); + } finally { + Binder.restoreCallingIdentity(ident); + } + } } - }; - - /* - * Split the calls from the device callback into local callback and the following chain: - * - replace the first CameraDevice arg with a CameraCaptureSession - * - duck type from device callback to session callback - * - then forward the call to a handler - * - then finally invoke the destination method on the session callback object - */ - if (callback == null) { - // OK: API allows the user to not specify a callback, and the handler may - // also be null in that case. Collapse whole dispatch chain to only call the local - // callback - return localCallback; - } - - InvokeDispatcher<CameraDeviceImpl.CaptureCallback> localSink = - new InvokeDispatcher<>(localCallback); - - InvokeDispatcher<CaptureCallback> userCallbackSink = - new InvokeDispatcher<>(callback); - HandlerDispatcher<CaptureCallback> handlerPassthrough = - new HandlerDispatcher<>(userCallbackSink, handler); - DuckTypingDispatcher<CameraDeviceImpl.CaptureCallback, CaptureCallback> duckToSession - = new DuckTypingDispatcher<>(handlerPassthrough, CaptureCallback.class); - ArgumentReplacingDispatcher<CameraDeviceImpl.CaptureCallback, CameraCaptureSessionImpl> - replaceDeviceWithSession = new ArgumentReplacingDispatcher<>(duckToSession, - /*argumentIndex*/0, this); - - BroadcastDispatcher<CameraDeviceImpl.CaptureCallback> broadcaster = - new BroadcastDispatcher<CameraDeviceImpl.CaptureCallback>( - replaceDeviceWithSession, - localSink); - - return new CallbackProxies.DeviceCaptureCallbackProxy(broadcaster); } /** * - * Create an internal state callback, to be invoked on the mDeviceHandler + * Create an internal state callback, to be invoked on the mDeviceExecutor * * <p>It has a few behaviors: * <ul> diff --git a/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java b/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java index 06c2c25a..3494a7f2 100644 --- a/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java +++ b/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java @@ -33,6 +33,7 @@ import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.concurrent.Executor; import static com.android.internal.util.Preconditions.*; @@ -59,14 +60,14 @@ public class CameraConstrainedHighSpeedCaptureSessionImpl * (e.g. no pending captures, no repeating requests, no flush).</p> */ CameraConstrainedHighSpeedCaptureSessionImpl(int id, - CameraCaptureSession.StateCallback callback, Handler stateHandler, + CameraCaptureSession.StateCallback callback, Executor stateExecutor, android.hardware.camera2.impl.CameraDeviceImpl deviceImpl, - Handler deviceStateHandler, boolean configureSuccess, + Executor deviceStateExecutor, boolean configureSuccess, CameraCharacteristics characteristics) { mCharacteristics = characteristics; CameraCaptureSession.StateCallback wrapperCallback = new WrapperCallback(callback); mSessionImpl = new CameraCaptureSessionImpl(id, /*input*/null, wrapperCallback, - stateHandler, deviceImpl, deviceStateHandler, configureSuccess); + stateExecutor, deviceImpl, deviceStateExecutor, configureSuccess); } @Override @@ -192,6 +193,13 @@ public class CameraConstrainedHighSpeedCaptureSessionImpl } @Override + public int captureSingleRequest(CaptureRequest request, Executor executor, + CaptureCallback listener) throws CameraAccessException { + throw new UnsupportedOperationException("Constrained high speed session doesn't support" + + " this method"); + } + + @Override public int captureBurst(List<CaptureRequest> requests, CaptureCallback listener, Handler handler) throws CameraAccessException { if (!isConstrainedHighSpeedRequestList(requests)) { @@ -203,6 +211,17 @@ public class CameraConstrainedHighSpeedCaptureSessionImpl } @Override + public int captureBurstRequests(List<CaptureRequest> requests, Executor executor, + CaptureCallback listener) throws CameraAccessException { + if (!isConstrainedHighSpeedRequestList(requests)) { + throw new IllegalArgumentException( + "Only request lists created by createHighSpeedRequestList() can be submitted to " + + "a constrained high speed capture session"); + } + return mSessionImpl.captureBurstRequests(requests, executor, listener); + } + + @Override public int setRepeatingRequest(CaptureRequest request, CaptureCallback listener, Handler handler) throws CameraAccessException { throw new UnsupportedOperationException("Constrained high speed session doesn't support" @@ -210,6 +229,13 @@ public class CameraConstrainedHighSpeedCaptureSessionImpl } @Override + public int setSingleRepeatingRequest(CaptureRequest request, Executor executor, + CaptureCallback listener) throws CameraAccessException { + throw new UnsupportedOperationException("Constrained high speed session doesn't support" + + " this method"); + } + + @Override public int setRepeatingBurst(List<CaptureRequest> requests, CaptureCallback listener, Handler handler) throws CameraAccessException { if (!isConstrainedHighSpeedRequestList(requests)) { @@ -221,6 +247,17 @@ public class CameraConstrainedHighSpeedCaptureSessionImpl } @Override + public int setRepeatingBurstRequests(List<CaptureRequest> requests, Executor executor, + CaptureCallback listener) throws CameraAccessException { + if (!isConstrainedHighSpeedRequestList(requests)) { + throw new IllegalArgumentException( + "Only request lists created by createHighSpeedRequestList() can be submitted to " + + "a constrained high speed capture session"); + } + return mSessionImpl.setRepeatingBurstRequests(requests, executor, listener); + } + + @Override public void stopRepeating() throws CameraAccessException { mSessionImpl.stopRepeating(); } diff --git a/android/hardware/camera2/impl/CameraDeviceImpl.java b/android/hardware/camera2/impl/CameraDeviceImpl.java index cab9d704..d967fbaf 100644 --- a/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -18,6 +18,7 @@ package android.hardware.camera2.impl; import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable; +import android.annotation.NonNull; import android.hardware.ICameraService; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; @@ -35,6 +36,7 @@ import android.hardware.camera2.params.SessionConfiguration; import android.hardware.camera2.params.StreamConfigurationMap; import android.hardware.camera2.utils.SubmitInfo; import android.hardware.camera2.utils.SurfaceUtils; +import android.os.Binder; import android.os.Build; import android.os.Handler; import android.os.IBinder; @@ -47,6 +49,8 @@ import android.util.Size; import android.util.SparseArray; import android.view.Surface; +import com.android.internal.util.Preconditions; + import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; import java.util.Collection; @@ -58,6 +62,8 @@ import java.util.List; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.Executor; + /** * HAL2.1+ implementation of CameraDevice. Use CameraManager#open to instantiate @@ -78,7 +84,7 @@ public class CameraDeviceImpl extends CameraDevice private final StateCallback mDeviceCallback; private volatile StateCallbackKK mSessionStateCallback; - private final Handler mDeviceHandler; + private final Executor mDeviceExecutor; private final AtomicBoolean mClosing = new AtomicBoolean(); private boolean mInError = false; @@ -234,14 +240,14 @@ public class CameraDeviceImpl extends CameraDevice } }; - public CameraDeviceImpl(String cameraId, StateCallback callback, Handler handler, + public CameraDeviceImpl(String cameraId, StateCallback callback, Executor executor, CameraCharacteristics characteristics, int appTargetSdkVersion) { - if (cameraId == null || callback == null || handler == null || characteristics == null) { + if (cameraId == null || callback == null || executor == null || characteristics == null) { throw new IllegalArgumentException("Null argument given"); } mCameraId = cameraId; mDeviceCallback = callback; - mDeviceHandler = handler; + mDeviceExecutor = executor; mCharacteristics = characteristics; mAppTargetSdkVersion = appTargetSdkVersion; @@ -288,15 +294,15 @@ public class CameraDeviceImpl extends CameraDevice try { remoteDeviceBinder.linkToDeath(this, /*flag*/ 0); } catch (RemoteException e) { - CameraDeviceImpl.this.mDeviceHandler.post(mCallOnDisconnected); + CameraDeviceImpl.this.mDeviceExecutor.execute(mCallOnDisconnected); throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED, "The camera device has encountered a serious error"); } } - mDeviceHandler.post(mCallOnOpened); - mDeviceHandler.post(mCallOnUnconfigured); + mDeviceExecutor.execute(mCallOnOpened); + mDeviceExecutor.execute(mCallOnUnconfigured); } } @@ -335,7 +341,7 @@ public class CameraDeviceImpl extends CameraDevice final boolean isError = failureIsError; synchronized(mInterfaceLock) { mInError = true; - mDeviceHandler.post(new Runnable() { + mDeviceExecutor.execute(new Runnable() { @Override public void run() { if (isError) { @@ -423,7 +429,7 @@ public class CameraDeviceImpl extends CameraDevice } } - mDeviceHandler.post(mCallOnBusy); + mDeviceExecutor.execute(mCallOnBusy); stopRepeating(); try { @@ -482,10 +488,10 @@ public class CameraDeviceImpl extends CameraDevice throw e; } finally { if (success && outputs.size() > 0) { - mDeviceHandler.post(mCallOnIdle); + mDeviceExecutor.execute(mCallOnIdle); } else { // Always return to the 'unconfigured' state if we didn't hit a fatal error - mDeviceHandler.post(mCallOnUnconfigured); + mDeviceExecutor.execute(mCallOnUnconfigured); } } } @@ -501,8 +507,9 @@ public class CameraDeviceImpl extends CameraDevice for (Surface surface : outputs) { outConfigurations.add(new OutputConfiguration(surface)); } - createCaptureSessionInternal(null, outConfigurations, callback, handler, - /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/ null); + createCaptureSessionInternal(null, outConfigurations, callback, + checkAndWrapHandler(handler), /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, + /*sessionParams*/ null); } @Override @@ -517,7 +524,7 @@ public class CameraDeviceImpl extends CameraDevice // OutputConfiguration objects are immutable, but need to have our own array List<OutputConfiguration> currentOutputs = new ArrayList<>(outputConfigurations); - createCaptureSessionInternal(null, currentOutputs, callback, handler, + createCaptureSessionInternal(null, currentOutputs, callback, checkAndWrapHandler(handler), /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/null); } @@ -537,8 +544,9 @@ public class CameraDeviceImpl extends CameraDevice for (Surface surface : outputs) { outConfigurations.add(new OutputConfiguration(surface)); } - createCaptureSessionInternal(inputConfig, outConfigurations, callback, handler, - /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/ null); + createCaptureSessionInternal(inputConfig, outConfigurations, callback, + checkAndWrapHandler(handler), /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, + /*sessionParams*/ null); } @Override @@ -566,8 +574,8 @@ public class CameraDeviceImpl extends CameraDevice currentOutputs.add(new OutputConfiguration(output)); } createCaptureSessionInternal(inputConfig, currentOutputs, - callback, handler, /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, - /*sessionParams*/ null); + callback, checkAndWrapHandler(handler), + /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/ null); } @Override @@ -582,7 +590,8 @@ public class CameraDeviceImpl extends CameraDevice for (Surface surface : outputs) { outConfigurations.add(new OutputConfiguration(surface)); } - createCaptureSessionInternal(null, outConfigurations, callback, handler, + createCaptureSessionInternal(null, outConfigurations, callback, + checkAndWrapHandler(handler), /*operatingMode*/ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE, /*sessionParams*/ null); } @@ -597,8 +606,8 @@ public class CameraDeviceImpl extends CameraDevice for (OutputConfiguration output : outputs) { currentOutputs.add(new OutputConfiguration(output)); } - createCaptureSessionInternal(inputConfig, currentOutputs, callback, handler, operatingMode, - /*sessionParams*/ null); + createCaptureSessionInternal(inputConfig, currentOutputs, callback, + checkAndWrapHandler(handler), operatingMode, /*sessionParams*/ null); } @Override @@ -612,14 +621,17 @@ public class CameraDeviceImpl extends CameraDevice if (outputConfigs == null) { throw new IllegalArgumentException("Invalid output configurations"); } + if (config.getExecutor() == null) { + throw new IllegalArgumentException("Invalid executor"); + } createCaptureSessionInternal(config.getInputConfiguration(), outputConfigs, - config.getStateCallback(), config.getHandler(), config.getSessionType(), + config.getStateCallback(), config.getExecutor(), config.getSessionType(), config.getSessionParameters()); } private void createCaptureSessionInternal(InputConfiguration inputConfig, List<OutputConfiguration> outputConfigurations, - CameraCaptureSession.StateCallback callback, Handler handler, + CameraCaptureSession.StateCallback callback, Executor executor, int operatingMode, CaptureRequest sessionParams) throws CameraAccessException { synchronized(mInterfaceLock) { if (DEBUG) { @@ -673,12 +685,11 @@ public class CameraDeviceImpl extends CameraDevice SurfaceUtils.checkConstrainedHighSpeedSurfaces(surfaces, /*fpsRange*/null, config); newSession = new CameraConstrainedHighSpeedCaptureSessionImpl(mNextSessionId++, - callback, handler, this, mDeviceHandler, configureSuccess, + callback, executor, this, mDeviceExecutor, configureSuccess, mCharacteristics); } else { newSession = new CameraCaptureSessionImpl(mNextSessionId++, input, - callback, handler, this, mDeviceHandler, - configureSuccess); + callback, executor, this, mDeviceExecutor, configureSuccess); } // TODO: wait until current session closes, then create the new session @@ -893,22 +904,22 @@ public class CameraDeviceImpl extends CameraDevice } } - public int capture(CaptureRequest request, CaptureCallback callback, Handler handler) + public int capture(CaptureRequest request, CaptureCallback callback, Executor executor) throws CameraAccessException { if (DEBUG) { Log.d(TAG, "calling capture"); } List<CaptureRequest> requestList = new ArrayList<CaptureRequest>(); requestList.add(request); - return submitCaptureRequest(requestList, callback, handler, /*streaming*/false); + return submitCaptureRequest(requestList, callback, executor, /*streaming*/false); } public int captureBurst(List<CaptureRequest> requests, CaptureCallback callback, - Handler handler) throws CameraAccessException { + Executor executor) throws CameraAccessException { if (requests == null || requests.isEmpty()) { throw new IllegalArgumentException("At least one request must be given"); } - return submitCaptureRequest(requests, callback, handler, /*streaming*/false); + return submitCaptureRequest(requests, callback, executor, /*streaming*/false); } /** @@ -963,7 +974,12 @@ public class CameraDeviceImpl extends CameraDevice } } }; - holder.getHandler().post(resultDispatch); + final long ident = Binder.clearCallingIdentity(); + try { + holder.getExecutor().execute(resultDispatch); + } finally { + Binder.restoreCallingIdentity(ident); + } } else { Log.w(TAG, String.format( "did not register callback to request %d", @@ -982,11 +998,11 @@ public class CameraDeviceImpl extends CameraDevice } private int submitCaptureRequest(List<CaptureRequest> requestList, CaptureCallback callback, - Handler handler, boolean repeating) throws CameraAccessException { + Executor executor, boolean repeating) throws CameraAccessException { - // Need a valid handler, or current thread needs to have a looper, if + // Need a valid executor, or current thread needs to have a looper, if // callback is valid - handler = checkHandler(handler, callback); + executor = checkExecutor(executor, callback); // Make sure that there all requests have at least 1 surface; all surfaces are non-null; // the surface isn't a physical stream surface for reprocessing request @@ -1001,19 +1017,17 @@ public class CameraDeviceImpl extends CameraDevice throw new IllegalArgumentException("Null Surface targets are not allowed"); } - if (!request.isReprocess()) { - continue; - } for (int i = 0; i < mConfiguredOutputs.size(); i++) { OutputConfiguration configuration = mConfiguredOutputs.valueAt(i); if (configuration.isForPhysicalCamera() && configuration.getSurfaces().contains(surface)) { - throw new IllegalArgumentException( - "Reprocess request on physical stream is not allowed"); + if (request.isReprocess()) { + throw new IllegalArgumentException( + "Reprocess request on physical stream is not allowed"); + } } } } - } synchronized(mInterfaceLock) { @@ -1042,7 +1056,7 @@ public class CameraDeviceImpl extends CameraDevice if (callback != null) { mCaptureCallbackMap.put(requestInfo.getRequestId(), new CaptureCallbackHolder( - callback, requestList, handler, repeating, mNextSessionId - 1)); + callback, requestList, executor, repeating, mNextSessionId - 1)); } else { if (DEBUG) { Log.d(TAG, "Listen for request " + requestInfo.getRequestId() + " is null"); @@ -1061,7 +1075,7 @@ public class CameraDeviceImpl extends CameraDevice } if (mIdle) { - mDeviceHandler.post(mCallOnActive); + mDeviceExecutor.execute(mCallOnActive); } mIdle = false; @@ -1070,18 +1084,18 @@ public class CameraDeviceImpl extends CameraDevice } public int setRepeatingRequest(CaptureRequest request, CaptureCallback callback, - Handler handler) throws CameraAccessException { + Executor executor) throws CameraAccessException { List<CaptureRequest> requestList = new ArrayList<CaptureRequest>(); requestList.add(request); - return submitCaptureRequest(requestList, callback, handler, /*streaming*/true); + return submitCaptureRequest(requestList, callback, executor, /*streaming*/true); } public int setRepeatingBurst(List<CaptureRequest> requests, CaptureCallback callback, - Handler handler) throws CameraAccessException { + Executor executor) throws CameraAccessException { if (requests == null || requests.isEmpty()) { throw new IllegalArgumentException("At least one request must be given"); } - return submitCaptureRequest(requests, callback, handler, /*streaming*/true); + return submitCaptureRequest(requests, callback, executor, /*streaming*/true); } public void stopRepeating() throws CameraAccessException { @@ -1126,12 +1140,12 @@ public class CameraDeviceImpl extends CameraDevice synchronized(mInterfaceLock) { checkIfCameraClosedOrInError(); - mDeviceHandler.post(mCallOnBusy); + mDeviceExecutor.execute(mCallOnBusy); // If already idle, just do a busy->idle transition immediately, don't actually // flush. if (mIdle) { - mDeviceHandler.post(mCallOnIdle); + mDeviceExecutor.execute(mCallOnIdle); return; } @@ -1159,7 +1173,7 @@ public class CameraDeviceImpl extends CameraDevice // either a normal close where the remote device is valid // or a close after a startup error (no remote device but in error state) if (mRemoteDevice != null || mInError) { - mDeviceHandler.post(mCallOnClosed); + mDeviceExecutor.execute(mCallOnClosed); } mRemoteDevice = null; @@ -1356,7 +1370,7 @@ public class CameraDeviceImpl extends CameraDevice private final boolean mRepeating; private final CaptureCallback mCallback; private final List<CaptureRequest> mRequestList; - private final Handler mHandler; + private final Executor mExecutor; private final int mSessionId; /** * <p>Determine if the callback holder is for a constrained high speed request list that @@ -1368,13 +1382,13 @@ public class CameraDeviceImpl extends CameraDevice private final boolean mHasBatchedOutputs; CaptureCallbackHolder(CaptureCallback callback, List<CaptureRequest> requestList, - Handler handler, boolean repeating, int sessionId) { - if (callback == null || handler == null) { + Executor executor, boolean repeating, int sessionId) { + if (callback == null || executor == null) { throw new UnsupportedOperationException( "Must have a valid handler and a valid callback"); } mRepeating = repeating; - mHandler = handler; + mExecutor = executor; mRequestList = new ArrayList<CaptureRequest>(requestList); mCallback = callback; mSessionId = sessionId; @@ -1427,8 +1441,8 @@ public class CameraDeviceImpl extends CameraDevice return getRequest(0); } - public Handler getHandler() { - return mHandler; + public Executor getExecutor() { + return mExecutor; } public int getSessionId() { @@ -1812,7 +1826,12 @@ public class CameraDeviceImpl extends CameraDevice } } }; - holder.getHandler().post(resultDispatch); + final long ident = Binder.clearCallingIdentity(); + try { + holder.getExecutor().execute(resultDispatch); + } finally { + Binder.restoreCallingIdentity(ident); + } } } } @@ -1840,7 +1859,12 @@ public class CameraDeviceImpl extends CameraDevice switch (errorCode) { case ERROR_CAMERA_DISCONNECTED: - CameraDeviceImpl.this.mDeviceHandler.post(mCallOnDisconnected); + final long ident = Binder.clearCallingIdentity(); + try { + CameraDeviceImpl.this.mDeviceExecutor.execute(mCallOnDisconnected); + } finally { + Binder.restoreCallingIdentity(ident); + } break; case ERROR_CAMERA_REQUEST: case ERROR_CAMERA_RESULT: @@ -1862,8 +1886,13 @@ public class CameraDeviceImpl extends CameraDevice private void scheduleNotifyError(int code) { mInError = true; - CameraDeviceImpl.this.mDeviceHandler.post(obtainRunnable( - CameraDeviceCallbacks::notifyError, this, code)); + final long ident = Binder.clearCallingIdentity(); + try { + CameraDeviceImpl.this.mDeviceExecutor.execute(obtainRunnable( + CameraDeviceCallbacks::notifyError, this, code)); + } finally { + Binder.restoreCallingIdentity(ident); + } } private void notifyError(int code) { @@ -1902,7 +1931,12 @@ public class CameraDeviceImpl extends CameraDevice if (mRemoteDevice == null) return; // Camera already closed if (!CameraDeviceImpl.this.mIdle) { - CameraDeviceImpl.this.mDeviceHandler.post(mCallOnIdle); + final long ident = Binder.clearCallingIdentity(); + try { + CameraDeviceImpl.this.mDeviceExecutor.execute(mCallOnIdle); + } finally { + Binder.restoreCallingIdentity(ident); + } } CameraDeviceImpl.this.mIdle = true; } @@ -1931,42 +1965,48 @@ public class CameraDeviceImpl extends CameraDevice if (isClosed()) return; // Dispatch capture start notice - holder.getHandler().post( - new Runnable() { - @Override - public void run() { - if (!CameraDeviceImpl.this.isClosed()) { - final int subsequenceId = resultExtras.getSubsequenceId(); - final CaptureRequest request = holder.getRequest(subsequenceId); - - if (holder.hasBatchedOutputs()) { - // Send derived onCaptureStarted for requests within the batch - final Range<Integer> fpsRange = - request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE); - for (int i = 0; i < holder.getRequestCount(); i++) { + final long ident = Binder.clearCallingIdentity(); + try { + holder.getExecutor().execute( + new Runnable() { + @Override + public void run() { + if (!CameraDeviceImpl.this.isClosed()) { + final int subsequenceId = resultExtras.getSubsequenceId(); + final CaptureRequest request = holder.getRequest(subsequenceId); + + if (holder.hasBatchedOutputs()) { + // Send derived onCaptureStarted for requests within the + // batch + final Range<Integer> fpsRange = + request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE); + for (int i = 0; i < holder.getRequestCount(); i++) { + holder.getCallback().onCaptureStarted( + CameraDeviceImpl.this, + holder.getRequest(i), + timestamp - (subsequenceId - i) * + NANO_PER_SECOND/fpsRange.getUpper(), + frameNumber - (subsequenceId - i)); + } + } else { holder.getCallback().onCaptureStarted( CameraDeviceImpl.this, - holder.getRequest(i), - timestamp - (subsequenceId - i) * - NANO_PER_SECOND/fpsRange.getUpper(), - frameNumber - (subsequenceId - i)); + holder.getRequest(resultExtras.getSubsequenceId()), + timestamp, frameNumber); } - } else { - holder.getCallback().onCaptureStarted( - CameraDeviceImpl.this, - holder.getRequest(resultExtras.getSubsequenceId()), - timestamp, frameNumber); } } - } - }); - + }); + } finally { + Binder.restoreCallingIdentity(ident); + } } } @Override public void onResultReceived(CameraMetadataNative result, - CaptureResultExtras resultExtras) throws RemoteException { + CaptureResultExtras resultExtras, PhysicalCaptureResultInfo physicalResults[]) + throws RemoteException { int requestId = resultExtras.getRequestId(); long frameNumber = resultExtras.getFrameNumber(); @@ -2073,7 +2113,8 @@ public class CameraDeviceImpl extends CameraDevice request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE); final int subsequenceId = resultExtras.getSubsequenceId(); final TotalCaptureResult resultAsCapture = new TotalCaptureResult(result, - request, resultExtras, partialResults, holder.getSessionId()); + request, resultExtras, partialResults, holder.getSessionId(), + physicalResults); // Final capture result resultDispatch = new Runnable() { @Override @@ -2088,9 +2129,11 @@ public class CameraDeviceImpl extends CameraDevice NANO_PER_SECOND/fpsRange.getUpper()); CameraMetadataNative resultLocal = new CameraMetadataNative(resultCopy); + // No logical multi-camera support for batched output mode. TotalCaptureResult resultInBatch = new TotalCaptureResult( resultLocal, holder.getRequest(i), resultExtras, - partialResults, holder.getSessionId()); + partialResults, holder.getSessionId(), + new PhysicalCaptureResultInfo[0]); holder.getCallback().onCaptureCompleted( CameraDeviceImpl.this, @@ -2109,7 +2152,12 @@ public class CameraDeviceImpl extends CameraDevice finalResult = resultAsCapture; } - holder.getHandler().post(resultDispatch); + final long ident = Binder.clearCallingIdentity(); + try { + holder.getExecutor().execute(resultDispatch); + } finally { + Binder.restoreCallingIdentity(ident); + } // Collect the partials for a total result; or mark the frame as totally completed mFrameNumberTracker.updateTracker(frameNumber, finalResult, isPartialResult, @@ -2205,7 +2253,12 @@ public class CameraDeviceImpl extends CameraDevice } }; // Dispatch the failure callback - holder.getHandler().post(failureDispatch); + final long ident = Binder.clearCallingIdentity(); + try { + holder.getExecutor().execute(failureDispatch); + } finally { + Binder.restoreCallingIdentity(ident); + } } } else { boolean mayHaveBuffers = (errorCode == ERROR_CAMERA_RESULT); @@ -2245,7 +2298,12 @@ public class CameraDeviceImpl extends CameraDevice checkAndFireSequenceComplete(); // Dispatch the failure callback - holder.getHandler().post(failureDispatch); + final long ident = Binder.clearCallingIdentity(); + try { + holder.getExecutor().execute(failureDispatch); + } finally { + Binder.restoreCallingIdentity(ident); + } } } @@ -2253,6 +2311,62 @@ public class CameraDeviceImpl extends CameraDevice } // public class CameraDeviceCallbacks /** + * A camera specific adapter {@link Executor} that posts all executed tasks onto the given + * {@link Handler}. + * + * @hide + */ + private static class CameraHandlerExecutor implements Executor { + private final Handler mHandler; + + public CameraHandlerExecutor(@NonNull Handler handler) { + mHandler = Preconditions.checkNotNull(handler); + } + + @Override + public void execute(Runnable command) { + // Return value of 'post()' will be ignored in order to keep the + // same camera behavior. For further details see b/74605221 . + mHandler.post(command); + } + } + + /** + * Default executor management. + * + * <p> + * If executor is null, get the current thread's + * Looper to create a Executor with. If no looper exists, throw + * {@code IllegalArgumentException}. + * </p> + */ + static Executor checkExecutor(Executor executor) { + return (executor == null) ? checkAndWrapHandler(null) : executor; + } + + /** + * Default executor management. + * + * <p>If the callback isn't null, check the executor, otherwise pass it through.</p> + */ + public static <T> Executor checkExecutor(Executor executor, T callback) { + return (callback != null) ? checkExecutor(executor) : executor; + } + + /** + * Wrap Handler in Executor. + * + * <p> + * If handler is null, get the current thread's + * Looper to create a Executor with. If no looper exists, throw + * {@code IllegalArgumentException}. + * </p> + */ + public static Executor checkAndWrapHandler(Handler handler) { + return new CameraHandlerExecutor(checkHandler(handler)); + } + + /** * Default handler management. * * <p> @@ -2326,6 +2440,11 @@ public class CameraDeviceImpl extends CameraDevice } } }; - CameraDeviceImpl.this.mDeviceHandler.post(r); + final long ident = Binder.clearCallingIdentity(); + try { + CameraDeviceImpl.this.mDeviceExecutor.execute(r); + } finally { + Binder.restoreCallingIdentity(ident); + } } } diff --git a/android/hardware/camera2/impl/CameraMetadataNative.java b/android/hardware/camera2/impl/CameraMetadataNative.java index ebe2fa17..e4b1339f 100644 --- a/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/android/hardware/camera2/impl/CameraMetadataNative.java @@ -22,12 +22,12 @@ import android.graphics.Rect; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureResult; -import android.hardware.camera2.marshal.Marshaler; import android.hardware.camera2.marshal.MarshalQueryable; import android.hardware.camera2.marshal.MarshalRegistry; +import android.hardware.camera2.marshal.Marshaler; import android.hardware.camera2.marshal.impl.MarshalQueryableArray; -import android.hardware.camera2.marshal.impl.MarshalQueryableBoolean; import android.hardware.camera2.marshal.impl.MarshalQueryableBlackLevelPattern; +import android.hardware.camera2.marshal.impl.MarshalQueryableBoolean; import android.hardware.camera2.marshal.impl.MarshalQueryableColorSpaceTransform; import android.hardware.camera2.marshal.impl.MarshalQueryableEnum; import android.hardware.camera2.marshal.impl.MarshalQueryableHighSpeedVideoConfiguration; @@ -48,6 +48,7 @@ import android.hardware.camera2.marshal.impl.MarshalQueryableString; import android.hardware.camera2.params.Face; import android.hardware.camera2.params.HighSpeedVideoConfiguration; import android.hardware.camera2.params.LensShadingMap; +import android.hardware.camera2.params.OisSample; import android.hardware.camera2.params.ReprocessFormatsMap; import android.hardware.camera2.params.StreamConfiguration; import android.hardware.camera2.params.StreamConfigurationDuration; @@ -56,8 +57,8 @@ import android.hardware.camera2.params.TonemapCurve; import android.hardware.camera2.utils.TypeReference; import android.location.Location; import android.location.LocationManager; -import android.os.Parcelable; import android.os.Parcel; +import android.os.Parcelable; import android.os.ServiceSpecificException; import android.util.Log; import android.util.Size; @@ -614,6 +615,15 @@ public class CameraMetadataNative implements Parcelable { return (T) metadata.getLensShadingMap(); } }); + sGetCommandMap.put( + CaptureResult.STATISTICS_OIS_SAMPLES.getNativeKey(), + new GetCommand() { + @Override + @SuppressWarnings("unchecked") + public <T> T getValue(CameraMetadataNative metadata, Key<T> key) { + return (T) metadata.getOisSamples(); + } + }); } private int[] getAvailableFormats() { @@ -962,6 +972,50 @@ public class CameraMetadataNative implements Parcelable { return tc; } + private OisSample[] getOisSamples() { + long[] timestamps = getBase(CaptureResult.STATISTICS_OIS_TIMESTAMPS); + float[] xShifts = getBase(CaptureResult.STATISTICS_OIS_X_SHIFTS); + float[] yShifts = getBase(CaptureResult.STATISTICS_OIS_Y_SHIFTS); + + if (timestamps == null) { + if (xShifts != null) { + throw new AssertionError("timestamps is null but xShifts is not"); + } + + if (yShifts != null) { + throw new AssertionError("timestamps is null but yShifts is not"); + } + + return null; + } + + if (xShifts == null) { + throw new AssertionError("timestamps is not null but xShifts is"); + } + + if (yShifts == null) { + throw new AssertionError("timestamps is not null but yShifts is"); + } + + if (xShifts.length != timestamps.length) { + throw new AssertionError(String.format( + "timestamps has %d entries but xShifts has %d", timestamps.length, + xShifts.length)); + } + + if (yShifts.length != timestamps.length) { + throw new AssertionError(String.format( + "timestamps has %d entries but yShifts has %d", timestamps.length, + yShifts.length)); + } + + OisSample[] samples = new OisSample[timestamps.length]; + for (int i = 0; i < timestamps.length; i++) { + samples[i] = new OisSample(timestamps[i], xShifts[i], yShifts[i]); + } + return samples; + } + private <T> void setBase(CameraCharacteristics.Key<T> key, T value) { setBase(key.getNativeKey(), value); } diff --git a/android/hardware/camera2/impl/PhysicalCaptureResultInfo.java b/android/hardware/camera2/impl/PhysicalCaptureResultInfo.java new file mode 100644 index 00000000..30eaf137 --- /dev/null +++ b/android/hardware/camera2/impl/PhysicalCaptureResultInfo.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2018 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.impl; + +import android.hardware.camera2.impl.CameraMetadataNative; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * @hide + */ +public class PhysicalCaptureResultInfo implements Parcelable { + private String cameraId; + private CameraMetadataNative cameraMetadata; + + public static final Parcelable.Creator<PhysicalCaptureResultInfo> CREATOR = + new Parcelable.Creator<PhysicalCaptureResultInfo>() { + @Override + public PhysicalCaptureResultInfo createFromParcel(Parcel in) { + return new PhysicalCaptureResultInfo(in); + } + + @Override + public PhysicalCaptureResultInfo[] newArray(int size) { + return new PhysicalCaptureResultInfo[size]; + } + }; + + private PhysicalCaptureResultInfo(Parcel in) { + readFromParcel(in); + } + + public PhysicalCaptureResultInfo(String cameraId, CameraMetadataNative cameraMetadata) { + this.cameraId = cameraId; + this.cameraMetadata = cameraMetadata; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(cameraId); + cameraMetadata.writeToParcel(dest, flags); + } + + public void readFromParcel(Parcel in) { + cameraId = in.readString(); + cameraMetadata = new CameraMetadataNative(); + cameraMetadata.readFromParcel(in); + } + + public String getCameraId() { + return cameraId; + } + + public CameraMetadataNative getCameraMetadata() { + return cameraMetadata; + } +} diff --git a/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/android/hardware/camera2/legacy/CameraDeviceUserShim.java index eccab750..bc7b1260 100644 --- a/android/hardware/camera2/legacy/CameraDeviceUserShim.java +++ b/android/hardware/camera2/legacy/CameraDeviceUserShim.java @@ -26,6 +26,7 @@ import android.hardware.camera2.ICameraDeviceCallbacks; import android.hardware.camera2.ICameraDeviceUser; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.impl.CaptureResultExtras; +import android.hardware.camera2.impl.PhysicalCaptureResultInfo; import android.hardware.camera2.params.OutputConfiguration; import android.hardware.camera2.utils.SubmitInfo; import android.os.ConditionVariable; @@ -249,7 +250,8 @@ public class CameraDeviceUserShim implements ICameraDeviceUser { @Override public void onResultReceived(final CameraMetadataNative result, - final CaptureResultExtras resultExtras) { + final CaptureResultExtras resultExtras, + PhysicalCaptureResultInfo physicalResults[]) { Object[] resultArray = new Object[] { result, resultExtras }; Message msg = getHandler().obtainMessage(RESULT_RECEIVED, /*obj*/ resultArray); @@ -320,7 +322,8 @@ public class CameraDeviceUserShim implements ICameraDeviceUser { Object[] resultArray = (Object[]) msg.obj; CameraMetadataNative result = (CameraMetadataNative) resultArray[0]; CaptureResultExtras resultExtras = (CaptureResultExtras) resultArray[1]; - mCallbacks.onResultReceived(result, resultExtras); + mCallbacks.onResultReceived(result, resultExtras, + new PhysicalCaptureResultInfo[0]); break; } case PREPARED: { diff --git a/android/hardware/camera2/legacy/LegacyCameraDevice.java b/android/hardware/camera2/legacy/LegacyCameraDevice.java index cb59fd14..71a361bd 100644 --- a/android/hardware/camera2/legacy/LegacyCameraDevice.java +++ b/android/hardware/camera2/legacy/LegacyCameraDevice.java @@ -23,6 +23,7 @@ import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.impl.CameraDeviceImpl; import android.hardware.camera2.impl.CaptureResultExtras; +import android.hardware.camera2.impl.PhysicalCaptureResultInfo; import android.hardware.camera2.ICameraDeviceCallbacks; import android.hardware.camera2.params.StreamConfigurationMap; import android.hardware.camera2.utils.ArrayUtils; @@ -253,7 +254,8 @@ public class LegacyCameraDevice implements AutoCloseable { holder.getRequestId()); } try { - mDeviceCallbacks.onResultReceived(result, extras); + mDeviceCallbacks.onResultReceived(result, extras, + new PhysicalCaptureResultInfo[0]); } catch (RemoteException e) { throw new IllegalStateException( "Received remote exception during onCameraError callback: ", e); @@ -728,7 +730,7 @@ public class LegacyCameraDevice implements AutoCloseable { LegacyExceptionUtils.throwOnError(nativeSetSurfaceDimens(surface, width, height)); } - static long getSurfaceId(Surface surface) throws BufferQueueAbandonedException { + public static long getSurfaceId(Surface surface) throws BufferQueueAbandonedException { checkNotNull(surface); try { return nativeGetSurfaceId(surface); diff --git a/android/hardware/camera2/legacy/LegacyExceptionUtils.java b/android/hardware/camera2/legacy/LegacyExceptionUtils.java index 93d6001c..55130c8f 100644 --- a/android/hardware/camera2/legacy/LegacyExceptionUtils.java +++ b/android/hardware/camera2/legacy/LegacyExceptionUtils.java @@ -62,14 +62,14 @@ public class LegacyExceptionUtils { * exceptions.</p> * * @param errorFlag error to throw as an exception. - * @throws {@link BufferQueueAbandonedException} for -ENODEV. + * @throws {@link BufferQueueAbandonedException} for BAD_VALUE. * @throws {@link UnsupportedOperationException} for an unknown negative error code. * @return {@code errorFlag} if the value was non-negative, throws otherwise. */ public static int throwOnError(int errorFlag) throws BufferQueueAbandonedException { if (errorFlag == NO_ERROR) { return NO_ERROR; - } else if (errorFlag == -ENODEV) { + } else if (errorFlag == BAD_VALUE) { throw new BufferQueueAbandonedException(); } diff --git a/android/hardware/camera2/params/OisSample.java b/android/hardware/camera2/params/OisSample.java new file mode 100644 index 00000000..7ebaae35 --- /dev/null +++ b/android/hardware/camera2/params/OisSample.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2018 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.CaptureResult; +import android.hardware.camera2.utils.HashCodeHelpers; + +import com.android.internal.util.Preconditions; + +/** + * Immutable class to store an + * {@link CaptureResult#STATISTICS_OIS_SAMPLES optical image stabilization sample}. + */ +public final class OisSample { + /** + * Create a new {@link OisSample}. + * + * <p>{@link OisSample} contains the timestamp and the amount of shifts in x and y direction, + * in pixels, of the OIS sample. + * + * <p>A positive value for a shift in x direction is a shift from left to right in active array + * coordinate system. For example, if the optical center is {@code (1000, 500)} in active array + * coordinates, a shift of {@code (3, 0)} puts the new optical center at {@code (1003, 500)}. + * </p> + * + * <p>A positive value for a shift in y direction is a shift from top to bottom in active array + * coordinate system. For example, if the optical center is {@code (1000, 500)} in active array + * coordinates, a shift of {@code (0, 5)} puts the new optical center at {@code (1000, 505)}. + * </p> + * + * <p>xShift and yShift must be finite; NaN and infinity is not allowed.</p> + * + * @param timestamp timestamp of the OIS sample. + * @param xShift shift of the OIS sample in x direction. + * @param yShift shift of the OIS sample in y direction. + * + * @throws IllegalArgumentException if xShift or yShift is not finite + */ + public OisSample(final long timestamp, final float xShift, final float yShift) { + mTimestampNs = timestamp; + mXShift = Preconditions.checkArgumentFinite(xShift, "xShift must be finite"); + mYShift = Preconditions.checkArgumentFinite(yShift, "yShift must be finite"); + } + + /** + * Get the timestamp in nanoseconds. + * + *<p>The timestamps are in the same timebase as and comparable to + *{@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp}.</p> + * + * @return a long value (guaranteed to be finite) + */ + public long getTimestamp() { + return mTimestampNs; + } + + /** + * Get the shift in x direction. + * + * @return a floating point value (guaranteed to be finite) + */ + public float getXshift() { + return mXShift; + } + + /** + * Get the shift in y direction. + * + * @return a floating point value (guaranteed to be finite) + */ + public float getYshift() { + return mYShift; + } + + /** + * Check if this {@link OisSample} is equal to another {@link OisSample}. + * + * <p>Two samples are only equal if and only if each of the OIS information is equal.</p> + * + * @return {@code true} if the objects were equal, {@code false} otherwise + */ + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } else if (this == obj) { + return true; + } else if (obj instanceof OisSample) { + final OisSample other = (OisSample) obj; + return mTimestampNs == other.mTimestampNs + && mXShift == other.mXShift + && mYShift == other.mYShift; + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + int timestampHash = HashCodeHelpers.hashCode(mTimestampNs); + return HashCodeHelpers.hashCode(mXShift, mYShift, timestampHash); + } + + /** + * Return the OisSample as a string representation. + * + * <p> {@code "OisSample{timestamp:%l, shift_x:%f, shift_y:%f}"} represents the OIS sample's + * timestamp, shift in x direction, and shift in y direction.</p> + * + * @return string representation of {@link OisSample} + */ + @Override + public String toString() { + return String.format("OisSample{timestamp:%d, shift_x:%f, shift_y:%f}", mTimestampNs, + mXShift, mYShift); + } + + private final long mTimestampNs; + private final float mXShift; + private final float mYShift; +} diff --git a/android/hardware/camera2/params/OutputConfiguration.java b/android/hardware/camera2/params/OutputConfiguration.java index f47cd665..a040a09c 100644 --- a/android/hardware/camera2/params/OutputConfiguration.java +++ b/android/hardware/camera2/params/OutputConfiguration.java @@ -82,11 +82,9 @@ import java.util.List; * * </ul> * - * <p>Please note that surface sharing is currently only enabled for outputs that use the - * {@link ImageFormat#PRIVATE} format. This includes surface sources like - * {@link android.view.SurfaceView}, {@link android.media.MediaRecorder}, - * {@link android.graphics.SurfaceTexture} and {@link android.media.ImageReader}, configured using - * the aforementioned format.</p> + * <p> As of {@link android.os.Build.VERSION_CODES#P Android P}, all formats can be used for + * sharing, subject to device support. On prior API levels, only {@link ImageFormat#PRIVATE} + * format may be used.</p> * * @see CameraDevice#createCaptureSessionByOutputConfigurations * @@ -368,7 +366,7 @@ public final class OutputConfiguration implements Parcelable { * desirable for the camera application to request streams from individual physical cameras. * This call achieves it by mapping the OutputConfiguration to the physical camera id.</p> * - * <p>The valid physical camera id can be queried by {@link + * <p>The valid physical camera ids can be queried by {@link * android.hardware.camera2.CameraCharacteristics#getPhysicalCameraIds}. * </p> * @@ -576,7 +574,7 @@ public final class OutputConfiguration implements Parcelable { * * @see #enableSurfaceSharing */ - public static int getMaxSharedSurfaceCount() { + public int getMaxSharedSurfaceCount() { return MAX_SURFACES_COUNT; } diff --git a/android/hardware/camera2/params/SessionConfiguration.java b/android/hardware/camera2/params/SessionConfiguration.java index a79a6c17..7bdb4a2f 100644 --- a/android/hardware/camera2/params/SessionConfiguration.java +++ b/android/hardware/camera2/params/SessionConfiguration.java @@ -17,10 +17,10 @@ package android.hardware.camera2.params; +import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.IntDef; -import android.os.Handler; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; @@ -31,6 +31,7 @@ import android.hardware.camera2.params.OutputConfiguration; import java.util.Collections; import java.util.List; import java.util.ArrayList; +import java.util.concurrent.Executor; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -78,7 +79,7 @@ public final class SessionConfiguration { private List<OutputConfiguration> mOutputConfigurations; private CameraCaptureSession.StateCallback mStateCallback; private int mSessionType; - private Handler mHandler = null; + private Executor mExecutor = null; private InputConfiguration mInputConfig = null; private CaptureRequest mSessionParameters = null; @@ -87,10 +88,9 @@ public final class SessionConfiguration { * * @param sessionType The session type. * @param outputs A list of output configurations for the capture session. + * @param executor The executor which should be used to invoke the callback. In general it is + * recommended that camera operations are not done on the main (UI) thread. * @param cb A state callback interface implementation. - * @param handler The handler on which the callback will be invoked. If it is - * set to null, the callback will be invoked on the current thread's - * {@link android.os.Looper looper}. * * @see #SESSION_REGULAR * @see #SESSION_HIGH_SPEED @@ -101,11 +101,12 @@ public final class SessionConfiguration { */ public SessionConfiguration(@SessionMode int sessionType, @NonNull List<OutputConfiguration> outputs, - @NonNull CameraCaptureSession.StateCallback cb, @Nullable Handler handler) { + @NonNull @CallbackExecutor Executor executor, + @NonNull CameraCaptureSession.StateCallback cb) { mSessionType = sessionType; mOutputConfigurations = Collections.unmodifiableList(new ArrayList<>(outputs)); mStateCallback = cb; - mHandler = handler; + mExecutor = executor; } /** @@ -136,14 +137,12 @@ public final class SessionConfiguration { } /** - * Retrieve the {@link CameraCaptureSession.StateCallback} for the capture session. + * Retrieve the {@link java.util.concurrent.Executor} for the capture session. * - * @return The handler on which the callback will be invoked. If it is - * set to null, the callback will be invoked on the current thread's - * {@link android.os.Looper looper}. + * @return The Executor on which the callback will be invoked. */ - public Handler getHandler() { - return mHandler; + public Executor getExecutor() { + return mExecutor; } /** diff --git a/android/hardware/camera2/utils/SurfaceUtils.java b/android/hardware/camera2/utils/SurfaceUtils.java index e1e1c4fd..92478441 100644 --- a/android/hardware/camera2/utils/SurfaceUtils.java +++ b/android/hardware/camera2/utils/SurfaceUtils.java @@ -56,6 +56,20 @@ public class SurfaceUtils { } /** + * Get the native object id of a surface. + * + * @param surface The surface to be checked. + * @return the native object id of the surface, 0 if surface is not backed by a native object. + */ + public static long getSurfaceId(Surface surface) { + try { + return LegacyCameraDevice.getSurfaceId(surface); + } catch (BufferQueueAbandonedException e) { + return 0; + } + } + + /** * Get the Surface size. * * @param surface The surface to be queried for size. diff --git a/android/hardware/camera2/utils/TaskDrainer.java b/android/hardware/camera2/utils/TaskDrainer.java index ed30ff34..e71f26a1 100644 --- a/android/hardware/camera2/utils/TaskDrainer.java +++ b/android/hardware/camera2/utils/TaskDrainer.java @@ -15,11 +15,11 @@ */ package android.hardware.camera2.utils; -import android.os.Handler; import android.util.Log; import java.util.HashSet; import java.util.Set; +import java.util.concurrent.Executor; import static com.android.internal.util.Preconditions.*; @@ -55,7 +55,7 @@ public class TaskDrainer<T> { private static final String TAG = "TaskDrainer"; private final boolean DEBUG = false; - private final Handler mHandler; + private final Executor mExecutor; private final DrainListener mListener; private final String mName; @@ -73,28 +73,27 @@ public class TaskDrainer<T> { /** * Create a new task drainer; {@code onDrained} callbacks will be posted to the listener - * via the {@code handler}. + * via the {@code executor}. * - * @param handler a non-{@code null} handler to use to post runnables to + * @param executor a non-{@code null} executor to use for listener execution * @param listener a non-{@code null} listener where {@code onDrained} will be called */ - public TaskDrainer(Handler handler, DrainListener listener) { - mHandler = checkNotNull(handler, "handler must not be null"); + public TaskDrainer(Executor executor, DrainListener listener) { + mExecutor = checkNotNull(executor, "executor must not be null"); mListener = checkNotNull(listener, "listener must not be null"); mName = null; } /** * Create a new task drainer; {@code onDrained} callbacks will be posted to the listener - * via the {@code handler}. + * via the {@code executor}. * - * @param handler a non-{@code null} handler to use to post runnables to + * @param executor a non-{@code null} executor to use for listener execution * @param listener a non-{@code null} listener where {@code onDrained} will be called * @param name an optional name used for debug logging */ - public TaskDrainer(Handler handler, DrainListener listener, String name) { - // XX: Probably don't need a handler at all here - mHandler = checkNotNull(handler, "handler must not be null"); + public TaskDrainer(Executor executor, DrainListener listener, String name) { + mExecutor = checkNotNull(executor, "executor must not be null"); mListener = checkNotNull(listener, "listener must not be null"); mName = name; } @@ -200,15 +199,12 @@ public class TaskDrainer<T> { } private void postDrained() { - mHandler.post(new Runnable() { - @Override - public void run() { + mExecutor.execute(() -> { if (DEBUG) { Log.v(TAG + "[" + mName + "]", "onDrained"); } mListener.onDrained(); - } }); } } diff --git a/android/hardware/camera2/utils/TaskSingleDrainer.java b/android/hardware/camera2/utils/TaskSingleDrainer.java index f6272c9e..9615450b 100644 --- a/android/hardware/camera2/utils/TaskSingleDrainer.java +++ b/android/hardware/camera2/utils/TaskSingleDrainer.java @@ -16,7 +16,8 @@ package android.hardware.camera2.utils; import android.hardware.camera2.utils.TaskDrainer.DrainListener; -import android.os.Handler; + +import java.util.concurrent.Executor; /** * Keep track of a single concurrent task starting and finishing; @@ -38,25 +39,25 @@ public class TaskSingleDrainer { /** * Create a new task drainer; {@code onDrained} callbacks will be posted to the listener - * via the {@code handler}. + * via the {@code executor}. * - * @param handler a non-{@code null} handler to use to post runnables to + * @param executor a non-{@code null} executor to use for listener execution * @param listener a non-{@code null} listener where {@code onDrained} will be called */ - public TaskSingleDrainer(Handler handler, DrainListener listener) { - mTaskDrainer = new TaskDrainer<>(handler, listener); + public TaskSingleDrainer(Executor executor, DrainListener listener) { + mTaskDrainer = new TaskDrainer<>(executor, listener); } /** * Create a new task drainer; {@code onDrained} callbacks will be posted to the listener - * via the {@code handler}. + * via the {@code executor}. * - * @param handler a non-{@code null} handler to use to post runnables to + * @param executor a non-{@code null} executor to use for listener execution * @param listener a non-{@code null} listener where {@code onDrained} will be called * @param name an optional name used for debug logging */ - public TaskSingleDrainer(Handler handler, DrainListener listener, String name) { - mTaskDrainer = new TaskDrainer<>(handler, listener, name); + public TaskSingleDrainer(Executor executor, DrainListener listener, String name) { + mTaskDrainer = new TaskDrainer<>(executor, listener, name); } /** diff --git a/android/hardware/display/AmbientBrightnessDayStats.java b/android/hardware/display/AmbientBrightnessDayStats.java new file mode 100644 index 00000000..1aa2557f --- /dev/null +++ b/android/hardware/display/AmbientBrightnessDayStats.java @@ -0,0 +1,240 @@ +/* + * Copyright 2018 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.display; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +import java.time.LocalDate; +import java.util.Arrays; + +/** + * AmbientBrightnessDayStats stores and manipulates brightness stats over a single day. + * {@see DisplayManager.getAmbientBrightnessStats()} + * + * @hide + */ +@SystemApi +@TestApi +public final class AmbientBrightnessDayStats implements Parcelable { + + /** The localdate for which brightness stats are being tracked */ + private final LocalDate mLocalDate; + + /** Ambient brightness values for creating bucket boundaries from */ + private final float[] mBucketBoundaries; + + /** Stats of how much time (in seconds) was spent in each of the buckets */ + private final float[] mStats; + + /** + * Initialize day stats from the given state. The time spent in each of the bucket is + * initialized to 0. + * + * @param localDate The date for which stats are being tracked + * @param bucketBoundaries Bucket boundaries used from creating the buckets from + * @hide + */ + public AmbientBrightnessDayStats(@NonNull LocalDate localDate, + @NonNull float[] bucketBoundaries) { + this(localDate, bucketBoundaries, null); + } + + /** + * Initialize day stats from the given state + * + * @param localDate The date for which stats are being tracked + * @param bucketBoundaries Bucket boundaries used from creating the buckets from + * @param stats Time spent in each of the buckets (in seconds) + * @hide + */ + public AmbientBrightnessDayStats(@NonNull LocalDate localDate, + @NonNull float[] bucketBoundaries, float[] stats) { + Preconditions.checkNotNull(localDate); + Preconditions.checkNotNull(bucketBoundaries); + Preconditions.checkArrayElementsInRange(bucketBoundaries, 0, Float.MAX_VALUE, + "bucketBoundaries"); + if (bucketBoundaries.length < 1) { + throw new IllegalArgumentException("Bucket boundaries must contain at least 1 value"); + } + checkSorted(bucketBoundaries); + if (stats == null) { + stats = new float[bucketBoundaries.length]; + } else { + Preconditions.checkArrayElementsInRange(stats, 0, Float.MAX_VALUE, "stats"); + if (bucketBoundaries.length != stats.length) { + throw new IllegalArgumentException( + "Bucket boundaries and stats must be of same size."); + } + } + mLocalDate = localDate; + mBucketBoundaries = bucketBoundaries; + mStats = stats; + } + + /** + * @return The {@link LocalDate} for which brightness stats are being tracked. + */ + public LocalDate getLocalDate() { + return mLocalDate; + } + + /** + * @return Aggregated stats of time spent (in seconds) in various buckets. + */ + public float[] getStats() { + return mStats; + } + + /** + * Returns the bucket boundaries (in lux) used for creating buckets. For eg., if the bucket + * boundaries array is {b1, b2, b3}, the buckets will be [b1, b2), [b2, b3), [b3, inf). + * + * @return The list of bucket boundaries. + */ + public float[] getBucketBoundaries() { + return mBucketBoundaries; + } + + private AmbientBrightnessDayStats(Parcel source) { + mLocalDate = LocalDate.parse(source.readString()); + mBucketBoundaries = source.createFloatArray(); + mStats = source.createFloatArray(); + } + + public static final Creator<AmbientBrightnessDayStats> CREATOR = + new Creator<AmbientBrightnessDayStats>() { + + @Override + public AmbientBrightnessDayStats createFromParcel(Parcel source) { + return new AmbientBrightnessDayStats(source); + } + + @Override + public AmbientBrightnessDayStats[] newArray(int size) { + return new AmbientBrightnessDayStats[size]; + } + }; + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + AmbientBrightnessDayStats other = (AmbientBrightnessDayStats) obj; + return mLocalDate.equals(other.mLocalDate) && Arrays.equals(mBucketBoundaries, + other.mBucketBoundaries) && Arrays.equals(mStats, other.mStats); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = result * prime + mLocalDate.hashCode(); + result = result * prime + Arrays.hashCode(mBucketBoundaries); + result = result * prime + Arrays.hashCode(mStats); + return result; + } + + @Override + public String toString() { + StringBuilder bucketBoundariesString = new StringBuilder(); + StringBuilder statsString = new StringBuilder(); + for (int i = 0; i < mBucketBoundaries.length; i++) { + if (i != 0) { + bucketBoundariesString.append(", "); + statsString.append(", "); + } + bucketBoundariesString.append(mBucketBoundaries[i]); + statsString.append(mStats[i]); + } + return new StringBuilder() + .append(mLocalDate).append(" ") + .append("{").append(bucketBoundariesString).append("} ") + .append("{").append(statsString).append("}").toString(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mLocalDate.toString()); + dest.writeFloatArray(mBucketBoundaries); + dest.writeFloatArray(mStats); + } + + /** + * Updates the stats by incrementing the time spent for the appropriate bucket based on ambient + * brightness reading. + * + * @param ambientBrightness Ambient brightness reading (in lux) + * @param durationSec Time spent with the given reading (in seconds) + * @hide + */ + public void log(float ambientBrightness, float durationSec) { + int bucketIndex = getBucketIndex(ambientBrightness); + if (bucketIndex >= 0) { + mStats[bucketIndex] += durationSec; + } + } + + private int getBucketIndex(float ambientBrightness) { + if (ambientBrightness < mBucketBoundaries[0]) { + return -1; + } + int low = 0; + int high = mBucketBoundaries.length - 1; + while (low < high) { + int mid = (low + high) / 2; + if (mBucketBoundaries[mid] <= ambientBrightness + && ambientBrightness < mBucketBoundaries[mid + 1]) { + return mid; + } else if (mBucketBoundaries[mid] < ambientBrightness) { + low = mid + 1; + } else if (mBucketBoundaries[mid] > ambientBrightness) { + high = mid - 1; + } + } + return low; + } + + private static void checkSorted(float[] values) { + if (values.length <= 1) { + return; + } + float prevValue = values[0]; + for (int i = 1; i < values.length; i++) { + Preconditions.checkState(prevValue < values[i]); + prevValue = values[i]; + } + return; + } +} diff --git a/android/hardware/display/BrightnessChangeEvent.java b/android/hardware/display/BrightnessChangeEvent.java index 2301824c..02eb28ce 100644 --- a/android/hardware/display/BrightnessChangeEvent.java +++ b/android/hardware/display/BrightnessChangeEvent.java @@ -54,19 +54,30 @@ public final class BrightnessChangeEvent implements Parcelable { /** Most recent battery level when brightness was changed or Float.NaN */ public final float batteryLevel; + /** Factor applied to brightness due to battery level, 0.0-1.0 */ + public final float powerBrightnessFactor; + /** Color filter active to provide night mode */ public final boolean nightMode; /** If night mode color filter is active this will be the temperature in kelvin */ public final int colorTemperature; - /** Brightness le vel before slider adjustment */ + /** Brightness level before slider adjustment */ public final float lastBrightness; + /** Whether brightness configuration is default version */ + public final boolean isDefaultBrightnessConfig; + + /** Whether brightness curve includes a user brightness point */ + public final boolean isUserSetBrightness; + + /** @hide */ private BrightnessChangeEvent(float brightness, long timeStamp, String packageName, int userId, float[] luxValues, long[] luxTimestamps, float batteryLevel, - boolean nightMode, int colorTemperature, float lastBrightness) { + float powerBrightnessFactor, boolean nightMode, int colorTemperature, + float lastBrightness, boolean isDefaultBrightnessConfig, boolean isUserSetBrightness) { this.brightness = brightness; this.timeStamp = timeStamp; this.packageName = packageName; @@ -74,9 +85,12 @@ public final class BrightnessChangeEvent implements Parcelable { this.luxValues = luxValues; this.luxTimestamps = luxTimestamps; this.batteryLevel = batteryLevel; + this.powerBrightnessFactor = powerBrightnessFactor; this.nightMode = nightMode; this.colorTemperature = colorTemperature; this.lastBrightness = lastBrightness; + this.isDefaultBrightnessConfig = isDefaultBrightnessConfig; + this.isUserSetBrightness = isUserSetBrightness; } /** @hide */ @@ -88,9 +102,12 @@ public final class BrightnessChangeEvent implements Parcelable { this.luxValues = other.luxValues; this.luxTimestamps = other.luxTimestamps; this.batteryLevel = other.batteryLevel; + this.powerBrightnessFactor = other.powerBrightnessFactor; this.nightMode = other.nightMode; this.colorTemperature = other.colorTemperature; this.lastBrightness = other.lastBrightness; + this.isDefaultBrightnessConfig = other.isDefaultBrightnessConfig; + this.isUserSetBrightness = other.isUserSetBrightness; } private BrightnessChangeEvent(Parcel source) { @@ -101,9 +118,12 @@ public final class BrightnessChangeEvent implements Parcelable { luxValues = source.createFloatArray(); luxTimestamps = source.createLongArray(); batteryLevel = source.readFloat(); + powerBrightnessFactor = source.readFloat(); nightMode = source.readBoolean(); colorTemperature = source.readInt(); lastBrightness = source.readFloat(); + isDefaultBrightnessConfig = source.readBoolean(); + isUserSetBrightness = source.readBoolean(); } public static final Creator<BrightnessChangeEvent> CREATOR = @@ -130,9 +150,12 @@ public final class BrightnessChangeEvent implements Parcelable { dest.writeFloatArray(luxValues); dest.writeLongArray(luxTimestamps); dest.writeFloat(batteryLevel); + dest.writeFloat(powerBrightnessFactor); dest.writeBoolean(nightMode); dest.writeInt(colorTemperature); dest.writeFloat(lastBrightness); + dest.writeBoolean(isDefaultBrightnessConfig); + dest.writeBoolean(isUserSetBrightness); } /** @hide */ @@ -144,9 +167,12 @@ public final class BrightnessChangeEvent implements Parcelable { private float[] mLuxValues; private long[] mLuxTimestamps; private float mBatteryLevel; + private float mPowerBrightnessFactor; private boolean mNightMode; private int mColorTemperature; private float mLastBrightness; + private boolean mIsDefaultBrightnessConfig; + private boolean mIsUserSetBrightness; /** {@see BrightnessChangeEvent#brightness} */ public Builder setBrightness(float brightness) { @@ -190,6 +216,12 @@ public final class BrightnessChangeEvent implements Parcelable { return this; } + /** {@see BrightnessChangeEvent#powerSaveBrightness} */ + public Builder setPowerBrightnessFactor(float powerBrightnessFactor) { + mPowerBrightnessFactor = powerBrightnessFactor; + return this; + } + /** {@see BrightnessChangeEvent#nightMode} */ public Builder setNightMode(boolean nightMode) { mNightMode = nightMode; @@ -208,11 +240,24 @@ public final class BrightnessChangeEvent implements Parcelable { return this; } + /** {@see BrightnessChangeEvent#isDefaultBrightnessConfig} */ + public Builder setIsDefaultBrightnessConfig(boolean isDefaultBrightnessConfig) { + mIsDefaultBrightnessConfig = isDefaultBrightnessConfig; + return this; + } + + /** {@see BrightnessChangeEvent#userBrightnessPoint} */ + public Builder setUserBrightnessPoint(boolean isUserSetBrightness) { + mIsUserSetBrightness = isUserSetBrightness; + return this; + } + /** Builds a BrightnessChangeEvent */ public BrightnessChangeEvent build() { return new BrightnessChangeEvent(mBrightness, mTimeStamp, mPackageName, mUserId, mLuxValues, mLuxTimestamps, mBatteryLevel, - mNightMode, mColorTemperature, mLastBrightness); + mPowerBrightnessFactor, mNightMode, mColorTemperature, mLastBrightness, + mIsDefaultBrightnessConfig, mIsUserSetBrightness); } } } diff --git a/android/hardware/display/DisplayManager.java b/android/hardware/display/DisplayManager.java index 4de4880b..efb9517a 100644 --- a/android/hardware/display/DisplayManager.java +++ b/android/hardware/display/DisplayManager.java @@ -28,7 +28,6 @@ import android.content.Context; import android.graphics.Point; import android.media.projection.MediaProjection; import android.os.Handler; -import android.os.UserHandle; import android.util.SparseArray; import android.view.Display; import android.view.Surface; @@ -536,6 +535,19 @@ public final class DisplayManager { } /** + * Set the level of color saturation to apply to the display. + * @param level The amount of saturation to apply, between 0 and 1 inclusive. + * 0 produces a grayscale image, 1 is normal. + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_SATURATION) + public void setSaturationLevel(float level) { + mGlobal.setSaturationLevel(level); + } + + /** * Creates a virtual display. * * @see #createVirtualDisplay(String, int, int, int, Surface, int, @@ -615,6 +627,7 @@ public final class DisplayManager { * @hide */ @SystemApi + @TestApi public Point getStableDisplaySize() { return mGlobal.getStableDisplaySize(); } @@ -631,6 +644,18 @@ public final class DisplayManager { } /** + * Fetch {@link AmbientBrightnessDayStats}s. + * + * @hide until we make it a system api + */ + @SystemApi + @TestApi + @RequiresPermission(Manifest.permission.ACCESS_AMBIENT_LIGHT_STATS) + public List<AmbientBrightnessDayStats> getAmbientBrightnessStats() { + return mGlobal.getAmbientBrightnessStats(); + } + + /** * Sets the global display brightness configuration. * * @hide @@ -639,7 +664,7 @@ public final class DisplayManager { @TestApi @RequiresPermission(Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfiguration(BrightnessConfiguration c) { - setBrightnessConfigurationForUser(c, UserHandle.myUserId(), mContext.getPackageName()); + setBrightnessConfigurationForUser(c, mContext.getUserId(), mContext.getPackageName()); } /** @@ -656,6 +681,45 @@ public final class DisplayManager { } /** + * Gets the global display brightness configuration or the default curve if one hasn't been set. + * + * @hide + */ + @SystemApi + @TestApi + @RequiresPermission(Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) + public BrightnessConfiguration getBrightnessConfiguration() { + return getBrightnessConfigurationForUser(mContext.getUserId()); + } + + /** + * Gets the global display brightness configuration or the default curve if one hasn't been set + * for a specific user. + * + * Note this requires the INTERACT_ACROSS_USERS permission if getting the configuration for a + * user other than the one you're currently running as. + * + * @hide + */ + public BrightnessConfiguration getBrightnessConfigurationForUser(int userId) { + return mGlobal.getBrightnessConfigurationForUser(userId); + } + + /** + * Gets the default global display brightness configuration or null one hasn't + * been configured. + * + * @hide + */ + @SystemApi + @TestApi + @RequiresPermission(Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) + @Nullable + public BrightnessConfiguration getDefaultBrightnessConfiguration() { + return mGlobal.getDefaultBrightnessConfiguration(); + } + + /** * Temporarily sets the brightness of the display. * <p> * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS} permission. diff --git a/android/hardware/display/DisplayManagerGlobal.java b/android/hardware/display/DisplayManagerGlobal.java index 2d5f5e04..2d0ef2f2 100644 --- a/android/hardware/display/DisplayManagerGlobal.java +++ b/android/hardware/display/DisplayManagerGlobal.java @@ -384,6 +384,17 @@ public final class DisplayManagerGlobal { } } + /** + * Set the level of color saturation to apply to the display. + */ + public void setSaturationLevel(float level) { + try { + mDm.setSaturationLevel(level); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + public VirtualDisplay createVirtualDisplay(Context context, MediaProjection projection, String name, int width, int height, int densityDpi, Surface surface, int flags, VirtualDisplay.Callback callback, Handler handler, String uniqueId) { @@ -490,6 +501,32 @@ public final class DisplayManagerGlobal { } /** + * Gets the global brightness configuration for a given user or null if one hasn't been set. + * + * @hide + */ + public BrightnessConfiguration getBrightnessConfigurationForUser(int userId) { + try { + return mDm.getBrightnessConfigurationForUser(userId); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** + * Gets the default brightness configuration or null if one hasn't been configured. + * + * @hide + */ + public BrightnessConfiguration getDefaultBrightnessConfiguration() { + try { + return mDm.getDefaultBrightnessConfiguration(); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** * Temporarily sets the brightness of the display. * <p> * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS} permission. @@ -525,6 +562,21 @@ public final class DisplayManagerGlobal { } } + /** + * Retrieves ambient brightness stats. + */ + public List<AmbientBrightnessDayStats> getAmbientBrightnessStats() { + try { + ParceledListSlice<AmbientBrightnessDayStats> stats = mDm.getAmbientBrightnessStats(); + if (stats == null) { + return Collections.emptyList(); + } + return stats.getList(); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub { @Override public void onDisplayEvent(int displayId, int event) { diff --git a/android/hardware/display/DisplayManagerInternal.java b/android/hardware/display/DisplayManagerInternal.java index 1cfad4f0..504f840a 100644 --- a/android/hardware/display/DisplayManagerInternal.java +++ b/android/hardware/display/DisplayManagerInternal.java @@ -23,6 +23,7 @@ import android.util.IntArray; import android.util.SparseArray; import android.view.Display; import android.view.DisplayInfo; +import android.view.SurfaceControl; /** * Display manager local system service interface. @@ -115,7 +116,7 @@ public abstract class DisplayManagerInternal { * Called by the window manager to perform traversals while holding a * surface flinger transaction. */ - public abstract void performTraversalInTransactionFromWindowManager(); + public abstract void performTraversal(SurfaceControl.Transaction t); /** * Tells the display manager about properties of the display that depend on the windows on it. @@ -174,9 +175,9 @@ public abstract class DisplayManagerInternal { public abstract boolean isUidPresentOnDisplay(int uid, int displayId); /** - * Persist brightness slider events. + * Persist brightness slider events and ambient brightness stats. */ - public abstract void persistBrightnessSliderEvents(); + public abstract void persistBrightnessTrackerState(); /** * Notifies the display manager that resource overlays have changed. diff --git a/android/hardware/display/WifiDisplay.java b/android/hardware/display/WifiDisplay.java index af5a84e6..bb32c199 100644 --- a/android/hardware/display/WifiDisplay.java +++ b/android/hardware/display/WifiDisplay.java @@ -19,7 +19,7 @@ package android.hardware.display; import android.os.Parcel; import android.os.Parcelable; -import libcore.util.Objects; +import java.util.Objects; /** * Describes the properties of a Wifi display. @@ -140,7 +140,7 @@ public final class WifiDisplay implements Parcelable { return other != null && mDeviceAddress.equals(other.mDeviceAddress) && mDeviceName.equals(other.mDeviceName) - && Objects.equal(mDeviceAlias, other.mDeviceAlias); + && Objects.equals(mDeviceAlias, other.mDeviceAlias); } /** diff --git a/android/hardware/fingerprint/Fingerprint.java b/android/hardware/fingerprint/Fingerprint.java index c3076347..c7ce8fad 100644 --- a/android/hardware/fingerprint/Fingerprint.java +++ b/android/hardware/fingerprint/Fingerprint.java @@ -15,6 +15,7 @@ */ package android.hardware.fingerprint; +import android.hardware.biometrics.BiometricAuthenticator; import android.os.Parcel; import android.os.Parcelable; @@ -22,7 +23,7 @@ import android.os.Parcelable; * Container for fingerprint metadata. * @hide */ -public final class Fingerprint implements Parcelable { +public final class Fingerprint extends BiometricAuthenticator.BiometricIdentifier { private CharSequence mName; private int mGroupId; private int mFingerId; diff --git a/android/hardware/fingerprint/FingerprintDialog.java b/android/hardware/fingerprint/FingerprintDialog.java deleted file mode 100644 index 6b7fab77..00000000 --- a/android/hardware/fingerprint/FingerprintDialog.java +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright (C) 2018 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.fingerprint; - -import static android.Manifest.permission.USE_FINGERPRINT; - -import android.annotation.CallbackExecutor; -import android.annotation.NonNull; -import android.annotation.RequiresPermission; -import android.content.Context; -import android.content.DialogInterface; -import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback; -import android.hardware.fingerprint.FingerprintManager.AuthenticationResult; -import android.hardware.fingerprint.IFingerprintDialogReceiver; -import android.os.Bundle; -import android.os.CancellationSignal; -import android.text.TextUtils; - -import java.util.concurrent.Executor; - -/** - * A class that manages a system-provided fingerprint dialog. - */ -public class FingerprintDialog { - - /** - * @hide - */ - public static final String KEY_TITLE = "title"; - /** - * @hide - */ - public static final String KEY_SUBTITLE = "subtitle"; - /** - * @hide - */ - public static final String KEY_DESCRIPTION = "description"; - /** - * @hide - */ - public static final String KEY_POSITIVE_TEXT = "positive_text"; - /** - * @hide - */ - public static final String KEY_NEGATIVE_TEXT = "negative_text"; - - /** - * Error/help message will show for this amount of time. - * For error messages, the dialog will also be dismissed after this amount of time. - * Error messages will be propagated back to the application via AuthenticationCallback - * after this amount of time. - * @hide - */ - public static final int HIDE_DIALOG_DELAY = 3000; // ms - /** - * @hide - */ - public static final int DISMISSED_REASON_POSITIVE = 1; - - /** - * @hide - */ - public static final int DISMISSED_REASON_NEGATIVE = 2; - - /** - * @hide - */ - public static final int DISMISSED_REASON_USER_CANCEL = 3; - - private static class ButtonInfo { - Executor executor; - DialogInterface.OnClickListener listener; - ButtonInfo(Executor ex, DialogInterface.OnClickListener l) { - executor = ex; - listener = l; - } - } - - /** - * A builder that collects arguments, to be shown on the system-provided fingerprint dialog. - **/ - public static class Builder { - private final Bundle bundle; - private ButtonInfo positiveButtonInfo; - private ButtonInfo negativeButtonInfo; - - /** - * Creates a builder for a fingerprint dialog. - */ - public Builder() { - bundle = new Bundle(); - } - - /** - * Required: Set the title to display. - * @param title - * @return - */ - public Builder setTitle(@NonNull CharSequence title) { - bundle.putCharSequence(KEY_TITLE, title); - return this; - } - - /** - * Optional: Set the subtitle to display. - * @param subtitle - * @return - */ - public Builder setSubtitle(@NonNull CharSequence subtitle) { - bundle.putCharSequence(KEY_SUBTITLE, subtitle); - return this; - } - - /** - * Optional: Set the description to display. - * @param description - * @return - */ - public Builder setDescription(@NonNull CharSequence description) { - bundle.putCharSequence(KEY_DESCRIPTION, description); - return this; - } - - /** - * Optional: Set the text for the positive button. If not set, the positive button - * will not show. - * @param text - * @return - * @hide - */ - public Builder setPositiveButton(@NonNull CharSequence text, - @NonNull @CallbackExecutor Executor executor, - @NonNull DialogInterface.OnClickListener listener) { - if (TextUtils.isEmpty(text)) { - throw new IllegalArgumentException("Text must be set and non-empty"); - } - if (executor == null) { - throw new IllegalArgumentException("Executor must not be null"); - } - if (listener == null) { - throw new IllegalArgumentException("Listener must not be null"); - } - bundle.putCharSequence(KEY_POSITIVE_TEXT, text); - positiveButtonInfo = new ButtonInfo(executor, listener); - return this; - } - - /** - * Required: Set the text for the negative button. - * @param text - * @return - */ - public Builder setNegativeButton(@NonNull CharSequence text, - @NonNull @CallbackExecutor Executor executor, - @NonNull DialogInterface.OnClickListener listener) { - if (TextUtils.isEmpty(text)) { - throw new IllegalArgumentException("Text must be set and non-empty"); - } - if (executor == null) { - throw new IllegalArgumentException("Executor must not be null"); - } - if (listener == null) { - throw new IllegalArgumentException("Listener must not be null"); - } - bundle.putCharSequence(KEY_NEGATIVE_TEXT, text); - negativeButtonInfo = new ButtonInfo(executor, listener); - return this; - } - - /** - * Creates a {@link FingerprintDialog} with the arguments supplied to this builder. - * @param context - * @return a {@link FingerprintDialog} - * @throws IllegalArgumentException if any of the required fields are not set. - */ - public FingerprintDialog build(Context context) { - final CharSequence title = bundle.getCharSequence(KEY_TITLE); - final CharSequence negative = bundle.getCharSequence(KEY_NEGATIVE_TEXT); - - if (TextUtils.isEmpty(title)) { - throw new IllegalArgumentException("Title must be set and non-empty"); - } else if (TextUtils.isEmpty(negative)) { - throw new IllegalArgumentException("Negative text must be set and non-empty"); - } - return new FingerprintDialog(context, bundle, positiveButtonInfo, negativeButtonInfo); - } - } - - private FingerprintManager mFingerprintManager; - private Bundle mBundle; - private ButtonInfo mPositiveButtonInfo; - private ButtonInfo mNegativeButtonInfo; - - IFingerprintDialogReceiver mDialogReceiver = new IFingerprintDialogReceiver.Stub() { - @Override - public void onDialogDismissed(int reason) { - // Check the reason and invoke OnClickListener(s) if necessary - if (reason == DISMISSED_REASON_POSITIVE) { - mPositiveButtonInfo.executor.execute(() -> { - mPositiveButtonInfo.listener.onClick(null, DialogInterface.BUTTON_POSITIVE); - }); - } else if (reason == DISMISSED_REASON_NEGATIVE) { - mNegativeButtonInfo.executor.execute(() -> { - mNegativeButtonInfo.listener.onClick(null, DialogInterface.BUTTON_NEGATIVE); - }); - } - } - }; - - private FingerprintDialog(Context context, Bundle bundle, - ButtonInfo positiveButtonInfo, ButtonInfo negativeButtonInfo) { - mBundle = bundle; - mPositiveButtonInfo = positiveButtonInfo; - mNegativeButtonInfo = negativeButtonInfo; - mFingerprintManager = context.getSystemService(FingerprintManager.class); - } - - /** - * This call warms up the fingerprint hardware, displays a system-provided dialog, - * and starts scanning for a fingerprint. It terminates when - * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} is called, when - * {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is called, - * when {@link AuthenticationCallback#onAuthenticationFailed()} is called or when the user - * dismisses the system-provided dialog, at which point the crypto object becomes invalid. - * This operation can be canceled by using the provided cancel object. The application will - * receive authentication errors through {@link AuthenticationCallback}, and button events - * through the corresponding callback set in - * {@link Builder#setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}. - * It is safe to reuse the {@link FingerprintDialog} object, and calling - * {@link FingerprintDialog#authenticate(CancellationSignal, Executor, AuthenticationCallback)} - * while an existing authentication attempt is occurring will stop the previous client and - * start a new authentication. The interrupted client will receive a cancelled notification - * through {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)}. - * - * @throws IllegalArgumentException if any of the arguments are null - * - * @param crypto object associated with the call - * @param cancel an object that can be used to cancel authentication - * @param executor an executor to handle callback events - * @param callback an object to receive authentication events - */ - @RequiresPermission(USE_FINGERPRINT) - public void authenticate(@NonNull FingerprintManager.CryptoObject crypto, - @NonNull CancellationSignal cancel, - @NonNull @CallbackExecutor Executor executor, - @NonNull FingerprintManager.AuthenticationCallback callback) { - mFingerprintManager.authenticate(crypto, cancel, mBundle, executor, mDialogReceiver, - callback); - } - - /** - * This call warms up the fingerprint hardware, displays a system-provided dialog, - * and starts scanning for a fingerprint. It terminates when - * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} is called, when - * {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is called, - * when {@link AuthenticationCallback#onAuthenticationFailed()} is called or when the user - * dismisses the system-provided dialog. This operation can be canceled by using the provided - * cancel object. The application will receive authentication errors through - * {@link AuthenticationCallback}, and button events through the corresponding callback set in - * {@link Builder#setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}. - * It is safe to reuse the {@link FingerprintDialog} object, and calling - * {@link FingerprintDialog#authenticate(CancellationSignal, Executor, AuthenticationCallback)} - * while an existing authentication attempt is occurring will stop the previous client and - * start a new authentication. The interrupted client will receive a cancelled notification - * through {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)}. - * - * @throws IllegalArgumentException if any of the arguments are null - * - * @param cancel an object that can be used to cancel authentication - * @param executor an executor to handle callback events - * @param callback an object to receive authentication events - */ - @RequiresPermission(USE_FINGERPRINT) - public void authenticate(@NonNull CancellationSignal cancel, - @NonNull @CallbackExecutor Executor executor, - @NonNull FingerprintManager.AuthenticationCallback callback) { - mFingerprintManager.authenticate(cancel, mBundle, executor, mDialogReceiver, callback); - } -} diff --git a/android/hardware/fingerprint/FingerprintManager.java b/android/hardware/fingerprint/FingerprintManager.java index 62d92c4a..a6c8c67d 100644 --- a/android/hardware/fingerprint/FingerprintManager.java +++ b/android/hardware/fingerprint/FingerprintManager.java @@ -18,15 +18,22 @@ package android.hardware.fingerprint; import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.Manifest.permission.MANAGE_FINGERPRINT; +import static android.Manifest.permission.USE_BIOMETRIC; import static android.Manifest.permission.USE_FINGERPRINT; import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.app.ActivityManager; import android.content.Context; +import android.content.pm.PackageManager; +import android.hardware.biometrics.BiometricAuthenticator; +import android.hardware.biometrics.BiometricDialog; +import android.hardware.biometrics.BiometricFingerprintConstants; +import android.hardware.biometrics.IBiometricDialogReceiver; import android.os.Binder; import android.os.Bundle; import android.os.CancellationSignal; @@ -38,7 +45,6 @@ import android.os.Looper; import android.os.PowerManager; import android.os.RemoteException; import android.os.UserHandle; -import android.security.keystore.AndroidKeyStoreProvider; import android.util.Log; import android.util.Slog; @@ -51,9 +57,15 @@ import javax.crypto.Mac; /** * A class that coordinates access to the fingerprint hardware. + * @deprecated See {@link BiometricDialog} which shows a system-provided dialog upon starting + * authentication. In a world where devices may have different types of biometric authentication, + * it's much more realistic to have a system-provided authentication dialog since the method may + * vary by vendor/device. */ +@Deprecated @SystemService(Context.FINGERPRINT_SERVICE) -public class FingerprintManager { +@RequiresFeature(PackageManager.FEATURE_FINGERPRINT) +public class FingerprintManager implements BiometricFingerprintConstants { private static final String TAG = "FingerprintManager"; private static final boolean DEBUG = true; private static final int MSG_ENROLL_RESULT = 100; @@ -64,147 +76,14 @@ public class FingerprintManager { private static final int MSG_REMOVED = 105; private static final int MSG_ENUMERATED = 106; - // - // Error messages from fingerprint hardware during initilization, enrollment, authentication or - // removal. Must agree with the list in fingerprint.h - // - - /** - * The hardware is unavailable. Try again later. - */ - public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; - - /** - * Error state returned when the sensor was unable to process the current image. - */ - public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; - - /** - * Error state returned when the current request has been running too long. This is intended to - * prevent programs from waiting for the fingerprint sensor indefinitely. The timeout is - * platform and sensor-specific, but is generally on the order of 30 seconds. - */ - public static final int FINGERPRINT_ERROR_TIMEOUT = 3; - - /** - * Error state returned for operations like enrollment; the operation cannot be completed - * because there's not enough storage remaining to complete the operation. - */ - public static final int FINGERPRINT_ERROR_NO_SPACE = 4; - - /** - * The operation was canceled because the fingerprint sensor is unavailable. For example, - * this may happen when the user is switched, the device is locked or another pending operation - * prevents or disables it. - */ - public static final int FINGERPRINT_ERROR_CANCELED = 5; - - /** - * The {@link FingerprintManager#remove} call failed. Typically this will happen when the - * provided fingerprint id was incorrect. - * - * @hide - */ - public static final int FINGERPRINT_ERROR_UNABLE_TO_REMOVE = 6; - - /** - * The operation was canceled because the API is locked out due to too many attempts. - * This occurs after 5 failed attempts, and lasts for 30 seconds. - */ - public static final int FINGERPRINT_ERROR_LOCKOUT = 7; - - /** - * Hardware vendors may extend this list if there are conditions that do not fall under one of - * the above categories. Vendors are responsible for providing error strings for these errors. - * These messages are typically reserved for internal operations such as enrollment, but may be - * used to express vendor errors not covered by the ones in fingerprint.h. Applications are - * expected to show the error message string if they happen, but are advised not to rely on the - * message id since they will be device and vendor-specific - */ - public static final int FINGERPRINT_ERROR_VENDOR = 8; - - /** - * The operation was canceled because FINGERPRINT_ERROR_LOCKOUT occurred too many times. - * Fingerprint authentication is disabled until the user unlocks with strong authentication - * (PIN/Pattern/Password) - */ - public static final int FINGERPRINT_ERROR_LOCKOUT_PERMANENT = 9; - - /** - * The user canceled the operation. Upon receiving this, applications should use alternate - * authentication (e.g. a password). The application should also provide the means to return - * to fingerprint authentication, such as a "use fingerprint" button. - */ - public static final int FINGERPRINT_ERROR_USER_CANCELED = 10; - - /** - * @hide - */ - public static final int FINGERPRINT_ERROR_VENDOR_BASE = 1000; - - // - // Image acquisition messages. Must agree with those in fingerprint.h - // - - /** - * The image acquired was good. - */ - public static final int FINGERPRINT_ACQUIRED_GOOD = 0; - - /** - * Only a partial fingerprint image was detected. During enrollment, the user should be - * informed on what needs to happen to resolve this problem, e.g. "press firmly on sensor." - */ - public static final int FINGERPRINT_ACQUIRED_PARTIAL = 1; - - /** - * The fingerprint image was too noisy to process due to a detected condition (i.e. dry skin) or - * a possibly dirty sensor (See {@link #FINGERPRINT_ACQUIRED_IMAGER_DIRTY}). - */ - public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; - - /** - * The fingerprint image was too noisy due to suspected or detected dirt on the sensor. - * For example, it's reasonable return this after multiple - * {@link #FINGERPRINT_ACQUIRED_INSUFFICIENT} or actual detection of dirt on the sensor - * (stuck pixels, swaths, etc.). The user is expected to take action to clean the sensor - * when this is returned. - */ - public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3; - - /** - * The fingerprint image was unreadable due to lack of motion. This is most appropriate for - * linear array sensors that require a swipe motion. - */ - public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 4; - - /** - * The fingerprint image was incomplete due to quick motion. While mostly appropriate for - * linear array sensors, this could also happen if the finger was moved during acquisition. - * The user should be asked to move the finger slower (linear) or leave the finger on the sensor - * longer. - */ - public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 5; - - /** - * Hardware vendors may extend this list if there are conditions that do not fall under one of - * the above categories. Vendors are responsible for providing error strings for these errors. - * @hide - */ - public static final int FINGERPRINT_ACQUIRED_VENDOR = 6; - /** - * @hide - */ - public static final int FINGERPRINT_ACQUIRED_VENDOR_BASE = 1000; - private IFingerprintService mService; private Context mContext; private IBinder mToken = new Binder(); - private AuthenticationCallback mAuthenticationCallback; + private BiometricAuthenticator.AuthenticationCallback mAuthenticationCallback; private EnrollmentCallback mEnrollmentCallback; private RemovalCallback mRemovalCallback; private EnumerateCallback mEnumerateCallback; - private CryptoObject mCryptoObject; + private android.hardware.biometrics.CryptoObject mCryptoObject; private Fingerprint mRemovalFingerprint; private Handler mHandler; private Executor mExecutor; @@ -217,9 +96,9 @@ public class FingerprintManager { } private class OnAuthenticationCancelListener implements OnCancelListener { - private CryptoObject mCrypto; + private android.hardware.biometrics.CryptoObject mCrypto; - public OnAuthenticationCancelListener(CryptoObject crypto) { + public OnAuthenticationCancelListener(android.hardware.biometrics.CryptoObject crypto) { mCrypto = crypto; } @@ -232,19 +111,20 @@ public class FingerprintManager { /** * A wrapper class for the crypto objects supported by FingerprintManager. Currently the * framework supports {@link Signature}, {@link Cipher} and {@link Mac} objects. + * @deprecated See {@link android.hardware.biometrics.BiometricDialog.CryptoObject} */ - public static final class CryptoObject { - + @Deprecated + public static final class CryptoObject extends android.hardware.biometrics.CryptoObject { public CryptoObject(@NonNull Signature signature) { - mCrypto = signature; + super(signature); } public CryptoObject(@NonNull Cipher cipher) { - mCrypto = cipher; + super(cipher); } public CryptoObject(@NonNull Mac mac) { - mCrypto = mac; + super(mac); } /** @@ -252,7 +132,7 @@ public class FingerprintManager { * @return {@link Signature} object or null if this doesn't contain one. */ public Signature getSignature() { - return mCrypto instanceof Signature ? (Signature) mCrypto : null; + return super.getSignature(); } /** @@ -260,7 +140,7 @@ public class FingerprintManager { * @return {@link Cipher} object or null if this doesn't contain one. */ public Cipher getCipher() { - return mCrypto instanceof Cipher ? (Cipher) mCrypto : null; + return super.getCipher(); } /** @@ -268,25 +148,16 @@ public class FingerprintManager { * @return {@link Mac} object or null if this doesn't contain one. */ public Mac getMac() { - return mCrypto instanceof Mac ? (Mac) mCrypto : null; - } - - /** - * @hide - * @return the opId associated with this object or 0 if none - */ - public long getOpId() { - return mCrypto != null ? - AndroidKeyStoreProvider.getKeyStoreOperationHandle(mCrypto) : 0; + return super.getMac(); } - - private final Object mCrypto; - }; + } /** * Container for callback data from {@link FingerprintManager#authenticate(CryptoObject, * CancellationSignal, int, AuthenticationCallback, Handler)}. + * @deprecated See {@link android.hardware.biometrics.BiometricDialog.AuthenticationResult} */ + @Deprecated public static class AuthenticationResult { private Fingerprint mFingerprint; private CryptoObject mCryptoObject; @@ -333,14 +204,18 @@ public class FingerprintManager { * FingerprintManager#authenticate(CryptoObject, CancellationSignal, * int, AuthenticationCallback, Handler) } must provide an implementation of this for listening to * fingerprint events. + * @deprecated See {@link android.hardware.biometrics.BiometricDialog.AuthenticationCallback} */ - public static abstract class AuthenticationCallback { + @Deprecated + public static abstract class AuthenticationCallback + extends BiometricAuthenticator.AuthenticationCallback { /** * Called when an unrecoverable error has been encountered and the operation is complete. * No further callbacks will be made on this object. * @param errorCode An integer identifying the error message * @param errString A human-readable error string that can be shown in UI */ + @Override public void onAuthenticationError(int errorCode, CharSequence errString) { } /** @@ -350,6 +225,7 @@ public class FingerprintManager { * @param helpCode An integer identifying the error message * @param helpString A human-readable string that can be shown in UI */ + @Override public void onAuthenticationHelp(int helpCode, CharSequence helpString) { } /** @@ -361,6 +237,7 @@ public class FingerprintManager { /** * Called when a fingerprint is valid but not recognized. */ + @Override public void onAuthenticationFailed() { } /** @@ -369,7 +246,19 @@ public class FingerprintManager { * @param acquireInfo one of FINGERPRINT_ACQUIRED_* constants * @hide */ + @Override public void onAuthenticationAcquired(int acquireInfo) {} + + /** + * @hide + * @param result + */ + @Override + public void onAuthenticationSucceeded(BiometricAuthenticator.AuthenticationResult result) { + onAuthenticationSucceeded(new AuthenticationResult( + (CryptoObject) result.getCryptoObject(), + (Fingerprint) result.getId(), result.getUserId())); + } }; /** @@ -489,11 +378,16 @@ public class FingerprintManager { * by <a href="{@docRoot}training/articles/keystore.html">Android Keystore * facility</a>. * @throws IllegalStateException if the crypto primitive is not initialized. + * @deprecated See {@link BiometricDialog#authenticate(CancellationSignal, Executor, + * BiometricDialog.AuthenticationCallback)} and {@link BiometricDialog#authenticate( + * BiometricDialog.CryptoObject, CancellationSignal, Executor, + * BiometricDialog.AuthenticationCallback)} */ - @RequiresPermission(USE_FINGERPRINT) + @Deprecated + @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT}) public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel, int flags, @NonNull AuthenticationCallback callback, @Nullable Handler handler) { - authenticate(crypto, cancel, flags, callback, handler, UserHandle.myUserId()); + authenticate(crypto, cancel, flags, callback, handler, mContext.getUserId()); } /** @@ -514,7 +408,7 @@ public class FingerprintManager { * @param userId the user ID that the fingerprint hardware will authenticate for. * @hide */ - @RequiresPermission(USE_FINGERPRINT) + @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT}) public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel, int flags, @NonNull AuthenticationCallback callback, Handler handler, int userId) { if (callback == null) { @@ -550,16 +444,16 @@ public class FingerprintManager { /** * Per-user version, see {@link FingerprintManager#authenticate(CryptoObject, - * CancellationSignal, Bundle, Executor, IFingerprintDialogReceiver, AuthenticationCallback)} + * CancellationSignal, Bundle, Executor, IBiometricDialogReceiver, AuthenticationCallback)} * @param userId the user ID that the fingerprint hardware will authenticate for. */ private void authenticate(int userId, - @Nullable CryptoObject crypto, + @Nullable android.hardware.biometrics.CryptoObject crypto, @NonNull CancellationSignal cancel, @NonNull Bundle bundle, @NonNull @CallbackExecutor Executor executor, - @NonNull IFingerprintDialogReceiver receiver, - @NonNull AuthenticationCallback callback) { + @NonNull IBiometricDialogReceiver receiver, + @NonNull BiometricAuthenticator.AuthenticationCallback callback) { mCryptoObject = crypto; if (cancel.isCanceled()) { Log.w(TAG, "authentication already canceled"); @@ -586,8 +480,8 @@ public class FingerprintManager { } /** - * Private method, see {@link FingerprintDialog#authenticate(CancellationSignal, Executor, - * AuthenticationCallback)} + * Private method, see {@link BiometricDialog#authenticate(CancellationSignal, Executor, + * BiometricDialog.AuthenticationCallback)} * @param cancel * @param executor * @param callback @@ -597,8 +491,8 @@ public class FingerprintManager { @NonNull CancellationSignal cancel, @NonNull Bundle bundle, @NonNull @CallbackExecutor Executor executor, - @NonNull IFingerprintDialogReceiver receiver, - @NonNull AuthenticationCallback callback) { + @NonNull IBiometricDialogReceiver receiver, + @NonNull BiometricAuthenticator.AuthenticationCallback callback) { if (cancel == null) { throw new IllegalArgumentException("Must supply a cancellation signal"); } @@ -614,24 +508,24 @@ public class FingerprintManager { if (callback == null) { throw new IllegalArgumentException("Must supply a calback"); } - authenticate(UserHandle.myUserId(), null, cancel, bundle, executor, receiver, callback); + authenticate(mContext.getUserId(), null, cancel, bundle, executor, receiver, callback); } /** - * Private method, see {@link FingerprintDialog#authenticate(CryptoObject, CancellationSignal, - * Executor, AuthenticationCallback)} + * Private method, see {@link BiometricDialog#authenticate(BiometricDialog.CryptoObject, + * CancellationSignal, Executor, BiometricDialog.AuthenticationCallback)} * @param crypto * @param cancel * @param executor * @param callback * @hide */ - public void authenticate(@NonNull CryptoObject crypto, + public void authenticate(@NonNull android.hardware.biometrics.CryptoObject crypto, @NonNull CancellationSignal cancel, @NonNull Bundle bundle, @NonNull @CallbackExecutor Executor executor, - @NonNull IFingerprintDialogReceiver receiver, - @NonNull AuthenticationCallback callback) { + @NonNull IBiometricDialogReceiver receiver, + @NonNull BiometricAuthenticator.AuthenticationCallback callback) { if (crypto == null) { throw new IllegalArgumentException("Must supply a crypto object"); } @@ -648,9 +542,10 @@ public class FingerprintManager { throw new IllegalArgumentException("Must supply a receiver"); } if (callback == null) { - throw new IllegalArgumentException("Must supply a calback"); + throw new IllegalArgumentException("Must supply a callback"); } - authenticate(UserHandle.myUserId(), crypto, cancel, bundle, executor, receiver, callback); + authenticate(mContext.getUserId(), crypto, cancel, + bundle, executor, receiver, callback); } /** @@ -841,19 +736,22 @@ public class FingerprintManager { */ @RequiresPermission(USE_FINGERPRINT) public List<Fingerprint> getEnrolledFingerprints() { - return getEnrolledFingerprints(UserHandle.myUserId()); + return getEnrolledFingerprints(mContext.getUserId()); } /** * Determine if there is at least one fingerprint enrolled. * * @return true if at least one fingerprint is enrolled, false otherwise + * @deprecated See {@link BiometricDialog} and + * {@link FingerprintManager#FINGERPRINT_ERROR_NO_FINGERPRINTS} */ + @Deprecated @RequiresPermission(USE_FINGERPRINT) public boolean hasEnrolledFingerprints() { if (mService != null) try { return mService.hasEnrolledFingerprints( - UserHandle.myUserId(), mContext.getOpPackageName()); + mContext.getUserId(), mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -879,7 +777,10 @@ public class FingerprintManager { * Determine if fingerprint hardware is present and functional. * * @return true if hardware is present and functional, false otherwise. + * @deprecated See {@link BiometricDialog} and + * {@link FingerprintManager#FINGERPRINT_ERROR_HW_UNAVAILABLE} */ + @Deprecated @RequiresPermission(USE_FINGERPRINT) public boolean isHardwareDetected() { if (mService != null) { @@ -1049,8 +950,8 @@ public class FingerprintManager { private void sendAuthenticatedSucceeded(Fingerprint fp, int userId) { if (mAuthenticationCallback != null) { - final AuthenticationResult result = - new AuthenticationResult(mCryptoObject, fp, userId); + final BiometricAuthenticator.AuthenticationResult result = + new BiometricAuthenticator.AuthenticationResult(mCryptoObject, fp, userId); mAuthenticationCallback.onAuthenticationSucceeded(result); } } @@ -1126,7 +1027,7 @@ public class FingerprintManager { } } - private void cancelAuthentication(CryptoObject cryptoObject) { + private void cancelAuthentication(android.hardware.biometrics.CryptoObject cryptoObject) { if (mService != null) try { mService.cancelAuthentication(mToken, mContext.getOpPackageName()); } catch (RemoteException e) { @@ -1160,6 +1061,12 @@ public class FingerprintManager { case FINGERPRINT_ERROR_USER_CANCELED: return mContext.getString( com.android.internal.R.string.fingerprint_error_user_canceled); + case FINGERPRINT_ERROR_NO_FINGERPRINTS: + return mContext.getString( + com.android.internal.R.string.fingerprint_error_no_fingerprints); + case FINGERPRINT_ERROR_HW_NOT_PRESENT: + return mContext.getString( + com.android.internal.R.string.fingerprint_error_hw_not_present); case FINGERPRINT_ERROR_VENDOR: { String[] msgArray = mContext.getResources().getStringArray( com.android.internal.R.array.fingerprint_error_vendor); @@ -1251,9 +1158,22 @@ public class FingerprintManager { @Override // binder call public void onError(long deviceId, int error, int vendorCode) { if (mExecutor != null) { - mExecutor.execute(() -> { - sendErrorResult(deviceId, error, vendorCode); - }); + // BiometricDialog case + if (error == FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED) { + // User tapped somewhere to cancel, the biometric dialog is already dismissed. + mExecutor.execute(() -> { + sendErrorResult(deviceId, error, vendorCode); + }); + } else { + // User got an error that needs to be displayed on the dialog, post a delayed + // runnable on the FingerprintManager handler that sends the error message after + // FingerprintDialog.HIDE_DIALOG_DELAY to send the error to the application. + mHandler.postDelayed(() -> { + mExecutor.execute(() -> { + sendErrorResult(deviceId, error, vendorCode); + }); + }, BiometricDialog.HIDE_DIALOG_DELAY); + } } else { mHandler.obtainMessage(MSG_ERROR, error, vendorCode, deviceId).sendToTarget(); } diff --git a/android/hardware/hdmi/HdmiControlManager.java b/android/hardware/hdmi/HdmiControlManager.java index a772cbe4..e34423c0 100644 --- a/android/hardware/hdmi/HdmiControlManager.java +++ b/android/hardware/hdmi/HdmiControlManager.java @@ -17,11 +17,13 @@ package android.hardware.hdmi; import android.annotation.Nullable; +import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SuppressLint; import android.content.Context; +import android.content.pm.PackageManager; import android.annotation.SystemApi; import android.annotation.SystemService; import android.os.RemoteException; @@ -42,6 +44,7 @@ import android.util.Log; */ @SystemApi @SystemService(Context.HDMI_CONTROL_SERVICE) +@RequiresFeature(PackageManager.FEATURE_HDMI_CEC) public final class HdmiControlManager { private static final String TAG = "HdmiControlManager"; diff --git a/android/hardware/input/InputManager.java b/android/hardware/input/InputManager.java index 1de8882e..6ae7a146 100644 --- a/android/hardware/input/InputManager.java +++ b/android/hardware/input/InputManager.java @@ -17,7 +17,6 @@ package android.hardware.input; import android.annotation.IntDef; -import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemService; @@ -43,8 +42,6 @@ import android.view.InputDevice; import android.view.InputEvent; import android.view.MotionEvent; import android.view.PointerIcon; -import android.view.inputmethod.InputMethodInfo; -import android.view.inputmethod.InputMethodSubtype; import com.android.internal.os.SomeArgs; @@ -703,52 +700,6 @@ public final class InputManager { } } - - /** - * Gets the keyboard layout for the specified input device and IME subtype. - * - * @param identifier The identifier for the input device. - * @param inputMethodInfo The input method. - * @param inputMethodSubtype The input method subtype. {@code null} if this input method does - * not support any subtype. - * - * @return The associated {@link KeyboardLayout}, or null if one has not been set. - * - * @hide - */ - @Nullable - public KeyboardLayout getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, - InputMethodInfo inputMethodInfo, @Nullable InputMethodSubtype inputMethodSubtype) { - try { - return mIm.getKeyboardLayoutForInputDevice( - identifier, inputMethodInfo, inputMethodSubtype); - } catch (RemoteException ex) { - throw ex.rethrowFromSystemServer(); - } - } - - /** - * Sets the keyboard layout for the specified input device and IME subtype pair. - * - * @param identifier The identifier for the input device. - * @param inputMethodInfo The input method with which to associate the keyboard layout. - * @param inputMethodSubtype The input method subtype which which to associate the keyboard - * layout. {@code null} if this input method does not support any subtype. - * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to set - * - * @hide - */ - public void setKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, - InputMethodInfo inputMethodInfo, @Nullable InputMethodSubtype inputMethodSubtype, - String keyboardLayoutDescriptor) { - try { - mIm.setKeyboardLayoutForInputDevice(identifier, inputMethodInfo, - inputMethodSubtype, keyboardLayoutDescriptor); - } catch (RemoteException ex) { - throw ex.rethrowFromSystemServer(); - } - } - /** * Gets the TouchCalibration applied to the specified input device's coordinates. * @@ -1246,7 +1197,7 @@ public final class InputManager { int repeat; if (effect instanceof VibrationEffect.OneShot) { VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect; - pattern = new long[] { 0, oneShot.getTiming() }; + pattern = new long[] { 0, oneShot.getDuration() }; repeat = -1; } else if (effect instanceof VibrationEffect.Waveform) { VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect; diff --git a/android/hardware/input/InputManagerInternal.java b/android/hardware/input/InputManagerInternal.java index 4ea0f552..eb7ea67e 100644 --- a/android/hardware/input/InputManagerInternal.java +++ b/android/hardware/input/InputManagerInternal.java @@ -16,11 +16,8 @@ package android.hardware.input; -import android.annotation.Nullable; import android.hardware.display.DisplayViewport; import android.view.InputEvent; -import android.view.inputmethod.InputMethodInfo; -import android.view.inputmethod.InputMethodSubtype; import java.util.List; @@ -46,16 +43,6 @@ public abstract class InputManagerInternal { public abstract void setInteractive(boolean interactive); /** - * Notifies that InputMethodManagerService switched the current input method subtype. - * - * @param userId user id that indicates who is using the specified input method and subtype. - * @param inputMethodInfo {@code null} when no input method is selected. - * @param subtype {@code null} when {@code inputMethodInfo} does has no subtype. - */ - public abstract void onInputMethodSubtypeChanged(int userId, - @Nullable InputMethodInfo inputMethodInfo, @Nullable InputMethodSubtype subtype); - - /** * Toggles Caps Lock state for input device with specific id. * * @param deviceId The id of input device. diff --git a/android/hardware/input/TouchCalibration.java b/android/hardware/input/TouchCalibration.java index 15503ed0..025fad04 100644 --- a/android/hardware/input/TouchCalibration.java +++ b/android/hardware/input/TouchCalibration.java @@ -123,10 +123,4 @@ public class TouchCalibration implements Parcelable { Float.floatToIntBits(mYScale) ^ Float.floatToIntBits(mYOffset); } - - @Override - public String toString() { - return String.format("[%f, %f, %f, %f, %f, %f]", - mXScale, mXYMix, mXOffset, mYXMix, mYScale, mYOffset); - } } diff --git a/android/hardware/location/ContextHubMessage.java b/android/hardware/location/ContextHubMessage.java index f85ce3ee..e1c69d71 100644 --- a/android/hardware/location/ContextHubMessage.java +++ b/android/hardware/location/ContextHubMessage.java @@ -33,7 +33,7 @@ import java.util.Arrays; */ @SystemApi @Deprecated -public class ContextHubMessage { +public class ContextHubMessage implements Parcelable { private static final int DEBUG_LOG_NUM_BYTES = 16; private int mType; private int mVersion; diff --git a/android/hardware/location/NanoApp.java b/android/hardware/location/NanoApp.java index b5c01ec2..ded1bb8c 100644 --- a/android/hardware/location/NanoApp.java +++ b/android/hardware/location/NanoApp.java @@ -36,7 +36,7 @@ import android.util.Log; */ @SystemApi @Deprecated -public class NanoApp { +public class NanoApp implements Parcelable { private final String TAG = "NanoApp"; private final String UNKNOWN = "Unknown"; diff --git a/android/hardware/location/NanoAppFilter.java b/android/hardware/location/NanoAppFilter.java index 75a96ee8..4d8e7344 100644 --- a/android/hardware/location/NanoAppFilter.java +++ b/android/hardware/location/NanoAppFilter.java @@ -28,7 +28,7 @@ import android.os.Parcelable; */ @SystemApi @Deprecated -public class NanoAppFilter { +public class NanoAppFilter implements Parcelable { private static final String TAG = "NanoAppFilter"; diff --git a/android/hardware/location/NanoAppInstanceInfo.java b/android/hardware/location/NanoAppInstanceInfo.java index f1926eaa..75fb9157 100644 --- a/android/hardware/location/NanoAppInstanceInfo.java +++ b/android/hardware/location/NanoAppInstanceInfo.java @@ -34,7 +34,7 @@ import libcore.util.EmptyArray; */ @SystemApi @Deprecated -public class NanoAppInstanceInfo { +public class NanoAppInstanceInfo implements Parcelable { private String mPublisher = "Unknown"; private String mName = "Unknown"; diff --git a/android/hardware/radio/ProgramList.java b/android/hardware/radio/ProgramList.java index b2aa9ba5..e6f523c0 100644 --- a/android/hardware/radio/ProgramList.java +++ b/android/hardware/radio/ProgramList.java @@ -263,6 +263,17 @@ public final class ProgramList implements AutoCloseable { /** * @hide for framework use only */ + public Filter() { + mIdentifierTypes = Collections.emptySet(); + mIdentifiers = Collections.emptySet(); + mIncludeCategories = false; + mExcludeModifications = false; + mVendorFilter = null; + } + + /** + * @hide for framework use only + */ public Filter(@Nullable Map<String, String> vendorFilter) { mIdentifierTypes = Collections.emptySet(); mIdentifiers = Collections.emptySet(); diff --git a/android/hardware/radio/ProgramSelector.java b/android/hardware/radio/ProgramSelector.java index 0294a29b..2a878ebb 100644 --- a/android/hardware/radio/ProgramSelector.java +++ b/android/hardware/radio/ProgramSelector.java @@ -441,6 +441,15 @@ public final class ProgramSelector implements Parcelable { */ public static @NonNull ProgramSelector createAmFmSelector( @RadioManager.Band int band, int frequencyKhz, int subChannel) { + if (band == RadioManager.BAND_INVALID) { + // 50MHz is a rough boundary between AM (<30MHz) and FM (>60MHz). + if (frequencyKhz < 50000) { + band = (subChannel <= 0) ? RadioManager.BAND_AM : RadioManager.BAND_AM_HD; + } else { + band = (subChannel <= 0) ? RadioManager.BAND_FM : RadioManager.BAND_FM_HD; + } + } + boolean isAm = (band == RadioManager.BAND_AM || band == RadioManager.BAND_AM_HD); boolean isDigital = (band == RadioManager.BAND_AM_HD || band == RadioManager.BAND_FM_HD); if (!isAm && !isDigital && band != RadioManager.BAND_FM) { diff --git a/android/hardware/radio/RadioManager.java b/android/hardware/radio/RadioManager.java index b00f6033..8263bb8d 100644 --- a/android/hardware/radio/RadioManager.java +++ b/android/hardware/radio/RadioManager.java @@ -21,10 +21,12 @@ import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; +import android.content.pm.PackageManager; import android.os.Handler; import android.os.Parcel; import android.os.Parcelable; @@ -58,6 +60,7 @@ import java.util.stream.Collectors; */ @SystemApi @SystemService(Context.RADIO_SERVICE) +@RequiresFeature(PackageManager.FEATURE_BROADCAST_RADIO) public class RadioManager { private static final String TAG = "BroadcastRadio.manager"; @@ -208,19 +211,23 @@ public class RadioManager { private final String mSerial; private final int mNumTuners; private final int mNumAudioSources; + private final boolean mIsInitializationRequired; private final boolean mIsCaptureSupported; private final BandDescriptor[] mBands; private final boolean mIsBgScanSupported; private final Set<Integer> mSupportedProgramTypes; private final Set<Integer> mSupportedIdentifierTypes; + @Nullable private final Map<String, Integer> mDabFrequencyTable; @NonNull private final Map<String, String> mVendorInfo; /** @hide */ public ModuleProperties(int id, String serviceName, int classId, String implementor, String product, String version, String serial, int numTuners, int numAudioSources, - boolean isCaptureSupported, BandDescriptor[] bands, boolean isBgScanSupported, + boolean isInitializationRequired, boolean isCaptureSupported, + BandDescriptor[] bands, boolean isBgScanSupported, @ProgramSelector.ProgramType int[] supportedProgramTypes, @ProgramSelector.IdentifierType int[] supportedIdentifierTypes, + @Nullable Map<String, Integer> dabFrequencyTable, Map<String, String> vendorInfo) { mId = id; mServiceName = TextUtils.isEmpty(serviceName) ? "default" : serviceName; @@ -231,11 +238,19 @@ public class RadioManager { mSerial = serial; mNumTuners = numTuners; mNumAudioSources = numAudioSources; + mIsInitializationRequired = isInitializationRequired; mIsCaptureSupported = isCaptureSupported; mBands = bands; mIsBgScanSupported = isBgScanSupported; mSupportedProgramTypes = arrayToSet(supportedProgramTypes); mSupportedIdentifierTypes = arrayToSet(supportedIdentifierTypes); + if (dabFrequencyTable != null) { + for (Map.Entry<String, Integer> entry : dabFrequencyTable.entrySet()) { + Objects.requireNonNull(entry.getKey()); + Objects.requireNonNull(entry.getValue()); + } + } + mDabFrequencyTable = dabFrequencyTable; mVendorInfo = (vendorInfo == null) ? new HashMap<>() : vendorInfo; } @@ -317,6 +332,18 @@ public class RadioManager { return mNumAudioSources; } + /** + * Checks, if BandConfig initialization (after {@link RadioManager#openTuner}) + * is required to be done before other operations or not. + * + * If it is, the client has to wait for {@link RadioTuner.Callback#onConfigurationChanged} + * callback before executing any other operations. Otherwise, such operation will fail + * returning {@link RadioManager#STATUS_INVALID_OPERATION} error code. + */ + public boolean isInitializationRequired() { + return mIsInitializationRequired; + } + /** {@code true} if audio capture is possible from radio tuner output. * This indicates if routing to audio devices not connected to the same HAL as the FM radio * is possible (e.g. to USB) or DAR (Digital Audio Recorder) feature can be implemented. @@ -363,6 +390,19 @@ public class RadioManager { } /** + * A frequency table for Digital Audio Broadcasting (DAB). + * + * The key is a channel name, i.e. 5A, 7B. + * + * The value is a frequency, in kHz. + * + * @return a frequency table, or {@code null} if the module doesn't support DAB + */ + public @Nullable Map<String, Integer> getDabFrequencyTable() { + return mDabFrequencyTable; + } + + /** * A map of vendor-specific opaque strings, passed from HAL without changes. * Format of these strings can vary across vendors. * @@ -394,6 +434,7 @@ public class RadioManager { mSerial = in.readString(); mNumTuners = in.readInt(); mNumAudioSources = in.readInt(); + mIsInitializationRequired = in.readInt() == 1; mIsCaptureSupported = in.readInt() == 1; Parcelable[] tmp = in.readParcelableArray(BandDescriptor.class.getClassLoader()); mBands = new BandDescriptor[tmp.length]; @@ -403,6 +444,7 @@ public class RadioManager { mIsBgScanSupported = in.readInt() == 1; mSupportedProgramTypes = arrayToSet(in.createIntArray()); mSupportedIdentifierTypes = arrayToSet(in.createIntArray()); + mDabFrequencyTable = Utils.readStringIntMap(in); mVendorInfo = Utils.readStringMap(in); } @@ -428,11 +470,13 @@ public class RadioManager { dest.writeString(mSerial); dest.writeInt(mNumTuners); dest.writeInt(mNumAudioSources); + dest.writeInt(mIsInitializationRequired ? 1 : 0); dest.writeInt(mIsCaptureSupported ? 1 : 0); dest.writeParcelableArray(mBands, flags); dest.writeInt(mIsBgScanSupported ? 1 : 0); dest.writeIntArray(setToArray(mSupportedProgramTypes)); dest.writeIntArray(setToArray(mSupportedIdentifierTypes)); + Utils.writeStringIntMap(dest, mDabFrequencyTable); Utils.writeStringMap(dest, mVendorInfo); } @@ -449,6 +493,7 @@ public class RadioManager { + ", mVersion=" + mVersion + ", mSerial=" + mSerial + ", mNumTuners=" + mNumTuners + ", mNumAudioSources=" + mNumAudioSources + + ", mIsInitializationRequired=" + mIsInitializationRequired + ", mIsCaptureSupported=" + mIsCaptureSupported + ", mIsBgScanSupported=" + mIsBgScanSupported + ", mBands=" + Arrays.toString(mBands) + "]"; @@ -456,67 +501,32 @@ public class RadioManager { @Override public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + mId; - result = prime * result + mServiceName.hashCode(); - result = prime * result + mClassId; - result = prime * result + ((mImplementor == null) ? 0 : mImplementor.hashCode()); - result = prime * result + ((mProduct == null) ? 0 : mProduct.hashCode()); - result = prime * result + ((mVersion == null) ? 0 : mVersion.hashCode()); - result = prime * result + ((mSerial == null) ? 0 : mSerial.hashCode()); - result = prime * result + mNumTuners; - result = prime * result + mNumAudioSources; - result = prime * result + (mIsCaptureSupported ? 1 : 0); - result = prime * result + Arrays.hashCode(mBands); - result = prime * result + (mIsBgScanSupported ? 1 : 0); - result = prime * result + mVendorInfo.hashCode(); - return result; + return Objects.hash(mId, mServiceName, mClassId, mImplementor, mProduct, mVersion, + mSerial, mNumTuners, mNumAudioSources, mIsInitializationRequired, + mIsCaptureSupported, mBands, mIsBgScanSupported, mDabFrequencyTable, mVendorInfo); } @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (!(obj instanceof ModuleProperties)) - return false; + if (this == obj) return true; + if (!(obj instanceof ModuleProperties)) return false; ModuleProperties other = (ModuleProperties) obj; - if (mId != other.getId()) - return false; + + if (mId != other.getId()) return false; if (!TextUtils.equals(mServiceName, other.mServiceName)) return false; - if (mClassId != other.getClassId()) - return false; - if (mImplementor == null) { - if (other.getImplementor() != null) - return false; - } else if (!mImplementor.equals(other.getImplementor())) - return false; - if (mProduct == null) { - if (other.getProduct() != null) - return false; - } else if (!mProduct.equals(other.getProduct())) - return false; - if (mVersion == null) { - if (other.getVersion() != null) - return false; - } else if (!mVersion.equals(other.getVersion())) - return false; - if (mSerial == null) { - if (other.getSerial() != null) - return false; - } else if (!mSerial.equals(other.getSerial())) - return false; - if (mNumTuners != other.getNumTuners()) - return false; - if (mNumAudioSources != other.getNumAudioSources()) - return false; - if (mIsCaptureSupported != other.isCaptureSupported()) - return false; - if (!Arrays.equals(mBands, other.getBands())) - return false; - if (mIsBgScanSupported != other.isBackgroundScanningSupported()) - return false; - if (!mVendorInfo.equals(other.mVendorInfo)) return false; + if (mClassId != other.mClassId) return false; + if (!Objects.equals(mImplementor, other.mImplementor)) return false; + if (!Objects.equals(mProduct, other.mProduct)) return false; + if (!Objects.equals(mVersion, other.mVersion)) return false; + if (!Objects.equals(mSerial, other.mSerial)) return false; + if (mNumTuners != other.mNumTuners) return false; + if (mNumAudioSources != other.mNumAudioSources) return false; + if (mIsInitializationRequired != other.mIsInitializationRequired) return false; + if (mIsCaptureSupported != other.mIsCaptureSupported) return false; + if (!Objects.equals(mBands, other.mBands)) return false; + if (mIsBgScanSupported != other.mIsBgScanSupported) return false; + if (!Objects.equals(mDabFrequencyTable, other.mDabFrequencyTable)) return false; + if (!Objects.equals(mVendorInfo, other.mVendorInfo)) return false; return true; } } diff --git a/android/hardware/radio/RadioMetadata.java b/android/hardware/radio/RadioMetadata.java index 3cc4b566..6e510607 100644 --- a/android/hardware/radio/RadioMetadata.java +++ b/android/hardware/radio/RadioMetadata.java @@ -96,6 +96,48 @@ public final class RadioMetadata implements Parcelable { */ public static final String METADATA_KEY_CLOCK = "android.hardware.radio.metadata.CLOCK"; + /** + * Technology-independent program name (station name). + */ + public static final String METADATA_KEY_PROGRAM_NAME = + "android.hardware.radio.metadata.PROGRAM_NAME"; + + /** + * DAB ensemble name. + */ + public static final String METADATA_KEY_DAB_ENSEMBLE_NAME = + "android.hardware.radio.metadata.DAB_ENSEMBLE_NAME"; + + /** + * DAB ensemble name - short version (up to 8 characters). + */ + public static final String METADATA_KEY_DAB_ENSEMBLE_NAME_SHORT = + "android.hardware.radio.metadata.DAB_ENSEMBLE_NAME_SHORT"; + + /** + * DAB service name. + */ + public static final String METADATA_KEY_DAB_SERVICE_NAME = + "android.hardware.radio.metadata.DAB_SERVICE_NAME"; + + /** + * DAB service name - short version (up to 8 characters). + */ + public static final String METADATA_KEY_DAB_SERVICE_NAME_SHORT = + "android.hardware.radio.metadata.DAB_SERVICE_NAME_SHORT"; + + /** + * DAB component name. + */ + public static final String METADATA_KEY_DAB_COMPONENT_NAME = + "android.hardware.radio.metadata.DAB_COMPONENT_NAME"; + + /** + * DAB component name. + */ + public static final String METADATA_KEY_DAB_COMPONENT_NAME_SHORT = + "android.hardware.radio.metadata.DAB_COMPONENT_NAME_SHORT"; + private static final int METADATA_TYPE_INVALID = -1; private static final int METADATA_TYPE_INT = 0; @@ -119,6 +161,13 @@ public final class RadioMetadata implements Parcelable { METADATA_KEYS_TYPE.put(METADATA_KEY_ICON, METADATA_TYPE_BITMAP); METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP); METADATA_KEYS_TYPE.put(METADATA_KEY_CLOCK, METADATA_TYPE_CLOCK); + METADATA_KEYS_TYPE.put(METADATA_KEY_PROGRAM_NAME, METADATA_TYPE_TEXT); + METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_ENSEMBLE_NAME, METADATA_TYPE_TEXT); + METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_ENSEMBLE_NAME_SHORT, METADATA_TYPE_TEXT); + METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_SERVICE_NAME, METADATA_TYPE_TEXT); + METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_SERVICE_NAME_SHORT, METADATA_TYPE_TEXT); + METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_COMPONENT_NAME, METADATA_TYPE_TEXT); + METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_COMPONENT_NAME_SHORT, METADATA_TYPE_TEXT); } // keep in sync with: system/media/radio/include/system/radio_metadata.h diff --git a/android/hardware/radio/RadioTuner.java b/android/hardware/radio/RadioTuner.java index ed20c4aa..0edd0553 100644 --- a/android/hardware/radio/RadioTuner.java +++ b/android/hardware/radio/RadioTuner.java @@ -64,7 +64,9 @@ public abstract class RadioTuner { * <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native * service fails, </li> * </ul> + * @deprecated Only applicable for HAL 1.x. */ + @Deprecated public abstract int setConfiguration(RadioManager.BandConfig config); /** @@ -80,7 +82,10 @@ public abstract class RadioTuner { * <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native * service fails, </li> * </ul> + * + * @deprecated Only applicable for HAL 1.x. */ + @Deprecated public abstract int getConfiguration(RadioManager.BandConfig[] config); @@ -228,7 +233,9 @@ public abstract class RadioTuner { * <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native * service fails, </li> * </ul> + * @deprecated Use {@link onProgramInfoChanged} callback instead. */ + @Deprecated public abstract int getProgramInformation(RadioManager.ProgramInfo[] info); /** @@ -427,7 +434,10 @@ public abstract class RadioTuner { * Get current antenna connection state for current configuration. * Only valid if a configuration has been applied. * @return {@code true} if the antenna is connected, {@code false} otherwise. + * + * @deprecated Use {@link onAntennaState} callback instead */ + @Deprecated public abstract boolean isAntennaConnected(); /** @@ -446,20 +456,41 @@ public abstract class RadioTuner { public abstract boolean hasControl(); /** Indicates a failure of radio IC or driver. - * The application must close and re open the tuner */ + * The application must close and re open the tuner + * @deprecated See {@link onError} callback. + */ + @Deprecated public static final int ERROR_HARDWARE_FAILURE = 0; /** Indicates a failure of the radio service. - * The application must close and re open the tuner */ + * The application must close and re open the tuner + * @deprecated See {@link onError} callback. + */ + @Deprecated public static final int ERROR_SERVER_DIED = 1; - /** A pending seek or tune operation was cancelled */ + /** A pending seek or tune operation was cancelled + * @deprecated See {@link onError} callback. + */ + @Deprecated public static final int ERROR_CANCELLED = 2; - /** A pending seek or tune operation timed out */ + /** A pending seek or tune operation timed out + * @deprecated See {@link onError} callback. + */ + @Deprecated public static final int ERROR_SCAN_TIMEOUT = 3; - /** The requested configuration could not be applied */ + /** The requested configuration could not be applied + * @deprecated See {@link onError} callback. + */ + @Deprecated public static final int ERROR_CONFIG = 4; - /** Background scan was interrupted due to hardware becoming temporarily unavailable. */ + /** Background scan was interrupted due to hardware becoming temporarily unavailable. + * @deprecated See {@link onError} callback. + */ + @Deprecated public static final int ERROR_BACKGROUND_SCAN_UNAVAILABLE = 5; - /** Background scan failed due to other error, ie. HW failure. */ + /** Background scan failed due to other error, ie. HW failure. + * @deprecated See {@link onError} callback. + */ + @Deprecated public static final int ERROR_BACKGROUND_SCAN_FAILED = 6; /** @@ -473,13 +504,29 @@ public abstract class RadioTuner { * status is one of {@link #ERROR_HARDWARE_FAILURE}, {@link #ERROR_SERVER_DIED}, * {@link #ERROR_CANCELLED}, {@link #ERROR_SCAN_TIMEOUT}, * {@link #ERROR_CONFIG} + * + * @deprecated Use {@link onTuneFailed} for tune, scan and step; + * other use cases (configuration, background scan) are already deprecated. */ public void onError(int status) {} + + /** + * Called when tune, scan or step operation fails. + * + * @param result cause of the failure + * @param selector ProgramSelector argument of tune that failed; + * null for scan and step. + */ + public void onTuneFailed(int result, @Nullable ProgramSelector selector) {} + /** * onConfigurationChanged() is called upon successful completion of * {@link RadioManager#openTuner(int, RadioManager.BandConfig, boolean, Callback, Handler)} * or {@link RadioTuner#setConfiguration(RadioManager.BandConfig)} + * + * @deprecated Only applicable for HAL 1.x. */ + @Deprecated public void onConfigurationChanged(RadioManager.BandConfig config) {} /** diff --git a/android/hardware/radio/TunerAdapter.java b/android/hardware/radio/TunerAdapter.java index 91944bfd..be2846f8 100644 --- a/android/hardware/radio/TunerAdapter.java +++ b/android/hardware/radio/TunerAdapter.java @@ -60,6 +60,7 @@ class TunerAdapter extends RadioTuner { mLegacyListProxy.close(); mLegacyListProxy = null; } + mCallback.close(); } try { mTuner.close(); @@ -202,15 +203,17 @@ class TunerAdapter extends RadioTuner { @Override public int getProgramInformation(RadioManager.ProgramInfo[] info) { if (info == null || info.length != 1) { - throw new IllegalArgumentException("The argument must be an array of length 1"); + Log.e(TAG, "The argument must be an array of length 1"); + return RadioManager.STATUS_BAD_VALUE; } - try { - info[0] = mTuner.getProgramInformation(); - return RadioManager.STATUS_OK; - } catch (RemoteException e) { - Log.e(TAG, "service died", e); - return RadioManager.STATUS_DEAD_OBJECT; + + RadioManager.ProgramInfo current = mCallback.getCurrentProgramInformation(); + if (current == null) { + Log.w(TAG, "Didn't get program info yet"); + return RadioManager.STATUS_INVALID_OPERATION; } + info[0] = current; + return RadioManager.STATUS_OK; } @Override @@ -276,6 +279,7 @@ class TunerAdapter extends RadioTuner { try { mTuner.startProgramListUpdates(filter); } catch (UnsupportedOperationException ex) { + Log.i(TAG, "Program list is not supported with this hardware"); return null; } catch (RemoteException ex) { mCallback.setProgramListObserver(null, () -> { }); @@ -288,12 +292,20 @@ class TunerAdapter extends RadioTuner { @Override public boolean isAnalogForced() { - return isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG); + try { + return isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG); + } catch (UnsupportedOperationException ex) { + throw new IllegalStateException(ex); + } } @Override public void setAnalogForced(boolean isForced) { - setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, isForced); + try { + setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, isForced); + } catch (UnsupportedOperationException ex) { + throw new IllegalStateException(ex); + } } @Override @@ -343,11 +355,7 @@ class TunerAdapter extends RadioTuner { @Override public boolean isAntennaConnected() { - try { - return mTuner.isAntennaConnected(); - } catch (RemoteException e) { - throw new RuntimeException("service died", e); - } + return mCallback.isAntennaConnected(); } @Override diff --git a/android/hardware/radio/TunerCallbackAdapter.java b/android/hardware/radio/TunerCallbackAdapter.java index b299ffe0..0fb93e53 100644 --- a/android/hardware/radio/TunerCallbackAdapter.java +++ b/android/hardware/radio/TunerCallbackAdapter.java @@ -37,8 +37,12 @@ class TunerCallbackAdapter extends ITunerCallback.Stub { @NonNull private final Handler mHandler; @Nullable ProgramList mProgramList; - @Nullable List<RadioManager.ProgramInfo> mLastCompleteList; // for legacy getProgramList call + + // cache for deprecated methods + boolean mIsAntennaConnected = true; + @Nullable List<RadioManager.ProgramInfo> mLastCompleteList; private boolean mDelayedCompleteCallback = false; + @Nullable RadioManager.ProgramInfo mCurrentProgramInfo; TunerCallbackAdapter(@NonNull RadioTuner.Callback callback, @Nullable Handler handler) { mCallback = callback; @@ -49,6 +53,12 @@ class TunerCallbackAdapter extends ITunerCallback.Stub { } } + void close() { + synchronized (mLock) { + if (mProgramList != null) mProgramList.close(); + } + } + void setProgramListObserver(@Nullable ProgramList programList, @NonNull ProgramList.OnCloseListener closeListener) { Objects.requireNonNull(closeListener); @@ -92,12 +102,46 @@ class TunerCallbackAdapter extends ITunerCallback.Stub { } } + @Nullable RadioManager.ProgramInfo getCurrentProgramInformation() { + synchronized (mLock) { + return mCurrentProgramInfo; + } + } + + boolean isAntennaConnected() { + return mIsAntennaConnected; + } + @Override public void onError(int status) { mHandler.post(() -> mCallback.onError(status)); } @Override + public void onTuneFailed(int status, @Nullable ProgramSelector selector) { + mHandler.post(() -> mCallback.onTuneFailed(status, selector)); + + int errorCode; + switch (status) { + case RadioManager.STATUS_PERMISSION_DENIED: + case RadioManager.STATUS_DEAD_OBJECT: + errorCode = RadioTuner.ERROR_SERVER_DIED; + break; + case RadioManager.STATUS_ERROR: + case RadioManager.STATUS_NO_INIT: + case RadioManager.STATUS_BAD_VALUE: + case RadioManager.STATUS_INVALID_OPERATION: + Log.i(TAG, "Got an error with no mapping to the legacy API (" + status + + "), doing a best-effort conversion to ERROR_SCAN_TIMEOUT"); + // fall through + case RadioManager.STATUS_TIMED_OUT: + default: + errorCode = RadioTuner.ERROR_SCAN_TIMEOUT; + } + mHandler.post(() -> mCallback.onError(errorCode)); + } + + @Override public void onConfigurationChanged(RadioManager.BandConfig config) { mHandler.post(() -> mCallback.onConfigurationChanged(config)); } @@ -109,6 +153,10 @@ class TunerCallbackAdapter extends ITunerCallback.Stub { return; } + synchronized (mLock) { + mCurrentProgramInfo = info; + } + mHandler.post(() -> { mCallback.onProgramInfoChanged(info); @@ -129,6 +177,7 @@ class TunerCallbackAdapter extends ITunerCallback.Stub { @Override public void onAntennaState(boolean connected) { + mIsAntennaConnected = connected; mHandler.post(() -> mCallback.onAntennaState(connected)); } diff --git a/android/hardware/radio/Utils.java b/android/hardware/radio/Utils.java index f1b58974..9887f782 100644 --- a/android/hardware/radio/Utils.java +++ b/android/hardware/radio/Utils.java @@ -56,6 +56,29 @@ final class Utils { return map; } + static void writeStringIntMap(@NonNull Parcel dest, @Nullable Map<String, Integer> map) { + if (map == null) { + dest.writeInt(0); + return; + } + dest.writeInt(map.size()); + for (Map.Entry<String, Integer> entry : map.entrySet()) { + dest.writeString(entry.getKey()); + dest.writeInt(entry.getValue()); + } + } + + static @NonNull Map<String, Integer> readStringIntMap(@NonNull Parcel in) { + int size = in.readInt(); + Map<String, Integer> map = new HashMap<>(); + while (size-- > 0) { + String key = in.readString(); + int value = in.readInt(); + map.put(key, value); + } + return map; + } + static <T extends Parcelable> void writeSet(@NonNull Parcel dest, @Nullable Set<T> set) { if (set == null) { dest.writeInt(0); diff --git a/android/hardware/soundtrigger/SoundTrigger.java b/android/hardware/soundtrigger/SoundTrigger.java index b635088c..dde8a332 100644 --- a/android/hardware/soundtrigger/SoundTrigger.java +++ b/android/hardware/soundtrigger/SoundTrigger.java @@ -16,6 +16,14 @@ package android.hardware.soundtrigger; +import static android.system.OsConstants.EINVAL; +import static android.system.OsConstants.ENODEV; +import static android.system.OsConstants.ENOSYS; +import static android.system.OsConstants.EPERM; +import static android.system.OsConstants.EPIPE; + +import android.annotation.Nullable; +import android.annotation.SystemApi; import android.media.AudioFormat; import android.os.Handler; import android.os.Parcel; @@ -25,22 +33,33 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.UUID; -import static android.system.OsConstants.*; - /** * The SoundTrigger class provides access via JNI to the native service managing * the sound trigger HAL. * * @hide */ +@SystemApi public class SoundTrigger { + private SoundTrigger() { + } + + /** + * Status code used when the operation succeeded + */ public static final int STATUS_OK = 0; + /** @hide */ public static final int STATUS_ERROR = Integer.MIN_VALUE; + /** @hide */ public static final int STATUS_PERMISSION_DENIED = -EPERM; + /** @hide */ public static final int STATUS_NO_INIT = -ENODEV; + /** @hide */ public static final int STATUS_BAD_VALUE = -EINVAL; + /** @hide */ public static final int STATUS_DEAD_OBJECT = -EPIPE; + /** @hide */ public static final int STATUS_INVALID_OPERATION = -ENOSYS; /***************************************************************************** @@ -48,6 +67,8 @@ public class SoundTrigger { * managed by the native sound trigger service. Each module has a unique * ID used to target any API call to this paricular module. Module * properties are returned by listModules() method. + * + * @hide ****************************************************************************/ public static class ModuleProperties implements Parcelable { /** Unique module ID provided by the native service */ @@ -187,6 +208,8 @@ public class SoundTrigger { * implementation to detect a particular sound pattern. * A specialized version {@link KeyphraseSoundModel} is defined for key phrase * sound models. + * + * @hide ****************************************************************************/ public static class SoundModel { /** Undefined sound model type */ @@ -261,6 +284,8 @@ public class SoundTrigger { /***************************************************************************** * A Keyphrase describes a key phrase that can be detected by a * {@link KeyphraseSoundModel} + * + * @hide ****************************************************************************/ public static class Keyphrase implements Parcelable { /** Unique identifier for this keyphrase */ @@ -382,6 +407,8 @@ public class SoundTrigger { * A KeyphraseSoundModel is a specialized {@link SoundModel} for key phrases. * It contains data needed by the hardware to detect a certain number of key phrases * and the list of corresponding {@link Keyphrase} descriptors. + * + * @hide ****************************************************************************/ public static class KeyphraseSoundModel extends SoundModel implements Parcelable { /** Key phrases in this sound model */ @@ -468,6 +495,8 @@ public class SoundTrigger { /***************************************************************************** * A GenericSoundModel is a specialized {@link SoundModel} for non-voice sound * patterns. + * + * @hide ****************************************************************************/ public static class GenericSoundModel extends SoundModel implements Parcelable { @@ -524,52 +553,115 @@ public class SoundTrigger { /** * Modes for key phrase recognition */ - /** Simple recognition of the key phrase */ + + /** + * Simple recognition of the key phrase + * + * @hide + */ public static final int RECOGNITION_MODE_VOICE_TRIGGER = 0x1; - /** Trigger only if one user is identified */ + /** + * Trigger only if one user is identified + * + * @hide + */ public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 0x2; - /** Trigger only if one user is authenticated */ + /** + * Trigger only if one user is authenticated + * + * @hide + */ public static final int RECOGNITION_MODE_USER_AUTHENTICATION = 0x4; /** * Status codes for {@link RecognitionEvent} */ - /** Recognition success */ + /** + * Recognition success + * + * @hide + */ public static final int RECOGNITION_STATUS_SUCCESS = 0; - /** Recognition aborted (e.g. capture preempted by anotehr use case */ + /** + * Recognition aborted (e.g. capture preempted by anotehr use case + * + * @hide + */ public static final int RECOGNITION_STATUS_ABORT = 1; - /** Recognition failure */ + /** + * Recognition failure + * + * @hide + */ public static final int RECOGNITION_STATUS_FAILURE = 2; /** * A RecognitionEvent is provided by the - * {@link StatusListener#onRecognition(RecognitionEvent)} + * {@code StatusListener#onRecognition(RecognitionEvent)} * callback upon recognition success or failure. */ - public static class RecognitionEvent implements Parcelable { - /** Recognition status e.g {@link #RECOGNITION_STATUS_SUCCESS} */ + public static class RecognitionEvent { + /** + * Recognition status e.g RECOGNITION_STATUS_SUCCESS + * + * @hide + */ public final int status; - /** Sound Model corresponding to this event callback */ + /** + * + * Sound Model corresponding to this event callback + * + * @hide + */ public final int soundModelHandle; - /** True if it is possible to capture audio from this utterance buffered by the hardware */ + /** + * True if it is possible to capture audio from this utterance buffered by the hardware + * + * @hide + */ public final boolean captureAvailable; - /** Audio session ID to be used when capturing the utterance with an AudioRecord - * if captureAvailable() is true. */ + /** + * Audio session ID to be used when capturing the utterance with an AudioRecord + * if captureAvailable() is true. + * + * @hide + */ public final int captureSession; - /** Delay in ms between end of model detection and start of audio available for capture. - * A negative value is possible (e.g. if keyphrase is also available for capture) */ + /** + * Delay in ms between end of model detection and start of audio available for capture. + * A negative value is possible (e.g. if keyphrase is also available for capture) + * + * @hide + */ public final int captureDelayMs; - /** Duration in ms of audio captured before the start of the trigger. 0 if none. */ + /** + * Duration in ms of audio captured before the start of the trigger. 0 if none. + * + * @hide + */ public final int capturePreambleMs; - /** True if the trigger (key phrase capture is present in binary data */ + /** + * True if the trigger (key phrase capture is present in binary data + * + * @hide + */ public final boolean triggerInData; - /** Audio format of either the trigger in event data or to use for capture of the - * rest of the utterance */ - public AudioFormat captureFormat; - /** Opaque data for use by system applications who know about voice engine internals, - * typically during enrollment. */ + /** + * Audio format of either the trigger in event data or to use for capture of the + * rest of the utterance + * + * @hide + */ + public final AudioFormat captureFormat; + /** + * Opaque data for use by system applications who know about voice engine internals, + * typically during enrollment. + * + * @hide + */ public final byte[] data; + /** @hide */ public RecognitionEvent(int status, int soundModelHandle, boolean captureAvailable, int captureSession, int captureDelayMs, int capturePreambleMs, boolean triggerInData, AudioFormat captureFormat, byte[] data) { @@ -584,6 +676,46 @@ public class SoundTrigger { this.data = data; } + /** + * Check if is possible to capture audio from this utterance buffered by the hardware. + * + * @return {@code true} iff a capturing is possible + */ + public boolean isCaptureAvailable() { + return captureAvailable; + } + + /** + * Get the audio format of either the trigger in event data or to use for capture of the + * rest of the utterance + * + * @return the audio format + */ + @Nullable public AudioFormat getCaptureFormat() { + return captureFormat; + } + + /** + * Get Audio session ID to be used when capturing the utterance with an {@link AudioRecord} + * if {@link #isCaptureAvailable()} is true. + * + * @return The id of the capture session + */ + public int getCaptureSession() { + return captureSession; + } + + /** + * Get the opaque data for use by system applications who know about voice engine + * internals, typically during enrollment. + * + * @return The data of the event + */ + public byte[] getData() { + return data; + } + + /** @hide */ public static final Parcelable.Creator<RecognitionEvent> CREATOR = new Parcelable.Creator<RecognitionEvent>() { public RecognitionEvent createFromParcel(Parcel in) { @@ -595,6 +727,7 @@ public class SoundTrigger { } }; + /** @hide */ protected static RecognitionEvent fromParcel(Parcel in) { int status = in.readInt(); int soundModelHandle = in.readInt(); @@ -619,12 +752,12 @@ public class SoundTrigger { captureDelayMs, capturePreambleMs, triggerInData, captureFormat, data); } - @Override + /** @hide */ public int describeContents() { return 0; } - @Override + /** @hide */ public void writeToParcel(Parcel dest, int flags) { dest.writeInt(status); dest.writeInt(soundModelHandle); @@ -726,6 +859,8 @@ public class SoundTrigger { * A RecognitionConfig is provided to * {@link SoundTriggerModule#startRecognition(int, RecognitionConfig)} to configure the * recognition request. + * + * @hide */ public static class RecognitionConfig implements Parcelable { /** True if the DSP should capture the trigger sound and make it available for further @@ -744,7 +879,7 @@ public class SoundTrigger { public final byte[] data; public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers, - KeyphraseRecognitionExtra keyphrases[], byte[] data) { + KeyphraseRecognitionExtra[] keyphrases, byte[] data) { this.captureRequested = captureRequested; this.allowMultipleTriggers = allowMultipleTriggers; this.keyphrases = keyphrases; @@ -799,6 +934,8 @@ public class SoundTrigger { * When used in a {@link RecognitionConfig} it indicates the minimum confidence level that * should trigger a recognition. * - The user ID is derived from the system ID {@link android.os.UserHandle#getIdentifier()}. + * + * @hide */ public static class ConfidenceLevel implements Parcelable { public final int userId; @@ -872,6 +1009,8 @@ public class SoundTrigger { /** * Additional data conveyed by a {@link KeyphraseRecognitionEvent} * for a key phrase detection. + * + * @hide */ public static class KeyphraseRecognitionExtra implements Parcelable { /** The keyphrase ID */ @@ -970,8 +1109,10 @@ public class SoundTrigger { /** * Specialized {@link RecognitionEvent} for a key phrase detection. + * + * @hide */ - public static class KeyphraseRecognitionEvent extends RecognitionEvent { + public static class KeyphraseRecognitionEvent extends RecognitionEvent implements Parcelable { /** Indicates if the key phrase is present in the buffered audio available for capture */ public final KeyphraseRecognitionExtra[] keyphraseExtras; @@ -1091,8 +1232,10 @@ public class SoundTrigger { /** * Sub-class of RecognitionEvent specifically for sound-trigger based sound * models(non-keyphrase). Currently does not contain any additional fields. + * + * @hide */ - public static class GenericRecognitionEvent extends RecognitionEvent { + public static class GenericRecognitionEvent extends RecognitionEvent implements Parcelable { public GenericRecognitionEvent(int status, int soundModelHandle, boolean captureAvailable, int captureSession, int captureDelayMs, int capturePreambleMs, boolean triggerInData, AudioFormat captureFormat, @@ -1140,13 +1283,19 @@ public class SoundTrigger { /** * Status codes for {@link SoundModelEvent} */ - /** Sound Model was updated */ + /** + * Sound Model was updated + * + * @hide + */ public static final int SOUNDMODEL_STATUS_UPDATED = 0; /** * A SoundModelEvent is provided by the * {@link StatusListener#onSoundModelUpdate(SoundModelEvent)} * callback when a sound model has been updated by the implementation + * + * @hide */ public static class SoundModelEvent implements Parcelable { /** Status e.g {@link #SOUNDMODEL_STATUS_UPDATED} */ @@ -1231,9 +1380,17 @@ public class SoundTrigger { * Native service state. {@link StatusListener#onServiceStateChange(int)} */ // Keep in sync with system/core/include/system/sound_trigger.h - /** Sound trigger service is enabled */ + /** + * Sound trigger service is enabled + * + * @hide + */ public static final int SERVICE_STATE_ENABLED = 0; - /** Sound trigger service is disabled */ + /** + * Sound trigger service is disabled + * + * @hide + */ public static final int SERVICE_STATE_DISABLED = 1; /** @@ -1245,6 +1402,8 @@ public class SoundTrigger { * - {@link #STATUS_NO_INIT} if the native service cannot be reached * - {@link #STATUS_BAD_VALUE} if modules is null * - {@link #STATUS_DEAD_OBJECT} if the binder transaction to the native service fails + * + * @hide */ public static native int listModules(ArrayList <ModuleProperties> modules); @@ -1256,6 +1415,8 @@ public class SoundTrigger { * @param handler the Handler that will receive the callabcks. Can be null if default handler * is OK. * @return a valid sound module in case of success or null in case of error. + * + * @hide */ public static SoundTriggerModule attachModule(int moduleId, StatusListener listener, @@ -1270,6 +1431,8 @@ public class SoundTrigger { /** * Interface provided by the client application when attaching to a {@link SoundTriggerModule} * to received recognition and error notifications. + * + * @hide */ public static interface StatusListener { /** diff --git a/android/hardware/usb/AccessoryFilter.java b/android/hardware/usb/AccessoryFilter.java index d9b7c5be..00070fe3 100644 --- a/android/hardware/usb/AccessoryFilter.java +++ b/android/hardware/usb/AccessoryFilter.java @@ -16,6 +16,11 @@ package android.hardware.usb; +import android.annotation.NonNull; +import android.service.usb.UsbAccessoryFilterProto; + +import com.android.internal.util.dump.DualDumpOutputStream; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -142,4 +147,17 @@ public class AccessoryFilter { "\", mModel=\"" + mModel + "\", mVersion=\"" + mVersion + "\"]"; } + + /** + * Write a description of the filter to a dump stream. + */ + public void dump(@NonNull DualDumpOutputStream dump, String idName, long id) { + long token = dump.start(idName, id); + + dump.write("manufacturer", UsbAccessoryFilterProto.MANUFACTURER, mManufacturer); + dump.write("model", UsbAccessoryFilterProto.MODEL, mModel); + dump.write("version", UsbAccessoryFilterProto.VERSION, mVersion); + + dump.end(token); + } } diff --git a/android/hardware/usb/DeviceFilter.java b/android/hardware/usb/DeviceFilter.java index 439c6297..6f1aff71 100644 --- a/android/hardware/usb/DeviceFilter.java +++ b/android/hardware/usb/DeviceFilter.java @@ -16,8 +16,12 @@ package android.hardware.usb; +import android.annotation.NonNull; +import android.service.usb.UsbDeviceFilterProto; import android.util.Slog; +import com.android.internal.util.dump.DualDumpOutputStream; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -310,4 +314,22 @@ public class DeviceFilter { ",mProductName=" + mProductName + ",mSerialNumber=" + mSerialNumber + "]"; } + + /** + * Write a description of the filter to a dump stream. + */ + public void dump(@NonNull DualDumpOutputStream dump, String idName, long id) { + long token = dump.start(idName, id); + + dump.write("vendor_id", UsbDeviceFilterProto.VENDOR_ID, mVendorId); + dump.write("product_id", UsbDeviceFilterProto.PRODUCT_ID, mProductId); + dump.write("class", UsbDeviceFilterProto.CLASS, mClass); + dump.write("subclass", UsbDeviceFilterProto.SUBCLASS, mSubclass); + dump.write("protocol", UsbDeviceFilterProto.PROTOCOL, mProtocol); + dump.write("manufacturer_name", UsbDeviceFilterProto.MANUFACTURER_NAME, mManufacturerName); + dump.write("product_name", UsbDeviceFilterProto.PRODUCT_NAME, mProductName); + dump.write("serial_number", UsbDeviceFilterProto.SERIAL_NUMBER, mSerialNumber); + + dump.end(token); + } } diff --git a/android/hardware/usb/UsbConfiguration.java b/android/hardware/usb/UsbConfiguration.java index a1715708..6ce42019 100644 --- a/android/hardware/usb/UsbConfiguration.java +++ b/android/hardware/usb/UsbConfiguration.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; + import com.android.internal.util.Preconditions; /** @@ -106,6 +107,17 @@ public class UsbConfiguration implements Parcelable { } /** + * Returns the attributes of this configuration + * + * @return the configuration's attributes + * + * @hide + */ + public int getAttributes() { + return mAttributes; + } + + /** * Returns the configuration's max power consumption, in milliamps. * * @return the configuration's max power diff --git a/android/hardware/usb/UsbConstants.java b/android/hardware/usb/UsbConstants.java index 0e8d47ca..215e9d5f 100644 --- a/android/hardware/usb/UsbConstants.java +++ b/android/hardware/usb/UsbConstants.java @@ -16,6 +16,8 @@ package android.hardware.usb; +import android.service.ServiceProtoEnums; + /** * Contains constants for the USB protocol. * These constants correspond to definitions in linux/usb/ch9.h in the linux kernel. @@ -35,12 +37,12 @@ public final class UsbConstants { * Used to signify direction of data for a {@link UsbEndpoint} is OUT (host to device) * @see UsbEndpoint#getDirection */ - public static final int USB_DIR_OUT = 0; + public static final int USB_DIR_OUT = ServiceProtoEnums.USB_ENDPOINT_DIR_OUT; // 0 /** * Used to signify direction of data for a {@link UsbEndpoint} is IN (device to host) * @see UsbEndpoint#getDirection */ - public static final int USB_DIR_IN = 0x80; + public static final int USB_DIR_IN = ServiceProtoEnums.USB_ENDPOINT_DIR_IN; // 0x80 /** * Bitmask used for extracting the {@link UsbEndpoint} number its address field. @@ -63,22 +65,26 @@ public final class UsbConstants { * Control endpoint type (endpoint zero) * @see UsbEndpoint#getType */ - public static final int USB_ENDPOINT_XFER_CONTROL = 0; + public static final int USB_ENDPOINT_XFER_CONTROL = + ServiceProtoEnums.USB_ENDPOINT_TYPE_XFER_CONTROL; // 0 /** * Isochronous endpoint type (currently not supported) * @see UsbEndpoint#getType */ - public static final int USB_ENDPOINT_XFER_ISOC = 1; + public static final int USB_ENDPOINT_XFER_ISOC = + ServiceProtoEnums.USB_ENDPOINT_TYPE_XFER_ISOC; // 1 /** * Bulk endpoint type * @see UsbEndpoint#getType */ - public static final int USB_ENDPOINT_XFER_BULK = 2; + public static final int USB_ENDPOINT_XFER_BULK = + ServiceProtoEnums.USB_ENDPOINT_TYPE_XFER_BULK; // 2 /** * Interrupt endpoint type * @see UsbEndpoint#getType */ - public static final int USB_ENDPOINT_XFER_INT = 3; + public static final int USB_ENDPOINT_XFER_INT = + ServiceProtoEnums.USB_ENDPOINT_TYPE_XFER_INT; // 3 /** diff --git a/android/hardware/usb/UsbDeviceConnection.java b/android/hardware/usb/UsbDeviceConnection.java index 5b15c0d2..9e5174ad 100644 --- a/android/hardware/usb/UsbDeviceConnection.java +++ b/android/hardware/usb/UsbDeviceConnection.java @@ -222,7 +222,10 @@ public class UsbDeviceConnection { * @param endpoint the endpoint for this transaction * @param buffer buffer for data to send or receive; can be {@code null} to wait for next * transaction without reading data - * @param length the length of the data to send or receive + * @param length the length of the data to send or receive. Before + * {@value Build.VERSION_CODES#P}, a value larger than 16384 bytes + * would be truncated down to 16384. In API {@value Build.VERSION_CODES#P} + * and after, any value of length is valid. * @param timeout in milliseconds, 0 is infinite * @return length of data transferred (or zero) for success, * or negative value for failure @@ -239,7 +242,10 @@ public class UsbDeviceConnection { * @param endpoint the endpoint for this transaction * @param buffer buffer for data to send or receive * @param offset the index of the first byte in the buffer to send or receive - * @param length the length of the data to send or receive + * @param length the length of the data to send or receive. Before + * {@value Build.VERSION_CODES#P}, a value larger than 16384 bytes + * would be truncated down to 16384. In API {@value Build.VERSION_CODES#P} + * and after, any value of length is valid. * @param timeout in milliseconds, 0 is infinite * @return length of data transferred (or zero) for success, * or negative value for failure @@ -247,6 +253,10 @@ public class UsbDeviceConnection { public int bulkTransfer(UsbEndpoint endpoint, byte[] buffer, int offset, int length, int timeout) { checkBounds(buffer, offset, length); + if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P + && length > UsbRequest.MAX_USBFS_BUFFER_SIZE) { + length = UsbRequest.MAX_USBFS_BUFFER_SIZE; + } return native_bulk_request(endpoint.getAddress(), buffer, offset, length, timeout); } diff --git a/android/hardware/usb/UsbManager.java b/android/hardware/usb/UsbManager.java index 7617c2bd..46142e35 100644 --- a/android/hardware/usb/UsbManager.java +++ b/android/hardware/usb/UsbManager.java @@ -19,6 +19,7 @@ package android.hardware.usb; import android.Manifest; import android.annotation.Nullable; +import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; @@ -27,7 +28,9 @@ import android.annotation.SystemService; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; +import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.hardware.usb.gadget.V1_0.GadgetFunction; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.Process; @@ -37,6 +40,8 @@ import android.util.Log; import com.android.internal.util.Preconditions; import java.util.HashMap; +import java.util.Map; +import java.util.StringJoiner; /** * This class allows you to access the state of USB and communicate with USB devices. @@ -70,7 +75,7 @@ public class UsbManager { * MTP function is enabled * <li> {@link #USB_FUNCTION_PTP} boolean extra indicating whether the * PTP function is enabled - * <li> {@link #USB_FUNCTION_PTP} boolean extra indicating whether the + * <li> {@link #USB_FUNCTION_ACCESSORY} boolean extra indicating whether the * accessory function is enabled * <li> {@link #USB_FUNCTION_AUDIO_SOURCE} boolean extra indicating whether the * audio source function is enabled @@ -187,17 +192,8 @@ public class UsbManager { public static final String USB_DATA_UNLOCKED = "unlocked"; /** - * Boolean extra indicating whether the intent represents a change in the usb - * configuration (as opposed to a state update). - * - * {@hide} - */ - public static final String USB_CONFIG_CHANGED = "config_changed"; - - /** * A placeholder indicating that no USB function is being specified. - * Used to distinguish between selecting no function vs. the default function in - * {@link #setCurrentFunction(String)}. + * Used for compatibility with old init scripts to indicate no functions vs. charging function. * * {@hide} */ @@ -298,6 +294,69 @@ public class UsbManager { */ public static final String EXTRA_PERMISSION_GRANTED = "permission"; + /** + * Code for the charging usb function. Passed into {@link #setCurrentFunctions(long)} + * {@hide} + */ + public static final long FUNCTION_NONE = 0; + + /** + * Code for the mtp usb function. Passed as a mask into {@link #setCurrentFunctions(long)} + * {@hide} + */ + public static final long FUNCTION_MTP = GadgetFunction.MTP; + + /** + * Code for the ptp usb function. Passed as a mask into {@link #setCurrentFunctions(long)} + * {@hide} + */ + public static final long FUNCTION_PTP = GadgetFunction.PTP; + + /** + * Code for the rndis usb function. Passed as a mask into {@link #setCurrentFunctions(long)} + * {@hide} + */ + public static final long FUNCTION_RNDIS = GadgetFunction.RNDIS; + + /** + * Code for the midi usb function. Passed as a mask into {@link #setCurrentFunctions(long)} + * {@hide} + */ + public static final long FUNCTION_MIDI = GadgetFunction.MIDI; + + /** + * Code for the accessory usb function. + * {@hide} + */ + public static final long FUNCTION_ACCESSORY = GadgetFunction.ACCESSORY; + + /** + * Code for the audio source usb function. + * {@hide} + */ + public static final long FUNCTION_AUDIO_SOURCE = GadgetFunction.AUDIO_SOURCE; + + /** + * Code for the adb usb function. + * {@hide} + */ + public static final long FUNCTION_ADB = GadgetFunction.ADB; + + private static final long SETTABLE_FUNCTIONS = FUNCTION_MTP | FUNCTION_PTP | FUNCTION_RNDIS + | FUNCTION_MIDI; + + private static final Map<String, Long> FUNCTION_NAME_TO_CODE = new HashMap<>(); + + static { + FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_MTP, FUNCTION_MTP); + FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_PTP, FUNCTION_PTP); + FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_RNDIS, FUNCTION_RNDIS); + FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_MIDI, FUNCTION_MIDI); + FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_ACCESSORY, FUNCTION_ACCESSORY); + FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_AUDIO_SOURCE, FUNCTION_AUDIO_SOURCE); + FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_ADB, FUNCTION_ADB); + } + private final Context mContext; private final IUsbManager mService; @@ -317,6 +376,7 @@ public class UsbManager { * * @return HashMap containing all connected USB devices. */ + @RequiresFeature(PackageManager.FEATURE_USB_HOST) public HashMap<String,UsbDevice> getDeviceList() { HashMap<String,UsbDevice> result = new HashMap<String,UsbDevice>(); if (mService == null) { @@ -341,6 +401,7 @@ public class UsbManager { * @param device the device to open * @return a {@link UsbDeviceConnection}, or {@code null} if open failed */ + @RequiresFeature(PackageManager.FEATURE_USB_HOST) public UsbDeviceConnection openDevice(UsbDevice device) { try { String deviceName = device.getDeviceName(); @@ -365,6 +426,7 @@ public class UsbManager { * * @return list of USB accessories, or null if none are attached. */ + @RequiresFeature(PackageManager.FEATURE_USB_ACCESSORY) public UsbAccessory[] getAccessoryList() { if (mService == null) { return null; @@ -384,9 +446,14 @@ public class UsbManager { /** * Opens a file descriptor for reading and writing data to the USB accessory. * + * <p>If data is read from the {@link java.io.InputStream} created from this file descriptor all + * data of a USB transfer should be read at once. If only a partial request is read the rest of + * the transfer is dropped. + * * @param accessory the USB accessory to open - * @return file descriptor, or null if the accessor could not be opened. + * @return file descriptor, or null if the accessory could not be opened. */ + @RequiresFeature(PackageManager.FEATURE_USB_ACCESSORY) public ParcelFileDescriptor openAccessory(UsbAccessory accessory) { try { return mService.openAccessory(accessory); @@ -396,6 +463,25 @@ public class UsbManager { } /** + * Gets the functionfs control file descriptor for the given function, with + * the usb descriptors and strings already written. The file descriptor is used + * by the function implementation to handle events and control requests. + * + * @param function to get control fd for. Currently {@link #FUNCTION_MTP} and + * {@link #FUNCTION_PTP} are supported. + * @return A ParcelFileDescriptor holding the valid fd, or null if the fd was not found. + * + * {@hide} + */ + public ParcelFileDescriptor getControlFd(long function) { + try { + return mService.getControlFd(function); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns true if the caller has permission to access the device. * Permission might have been granted temporarily via * {@link #requestPermission(UsbDevice, PendingIntent)} or @@ -407,6 +493,7 @@ public class UsbManager { * @param device to check permissions for * @return true if caller has permission */ + @RequiresFeature(PackageManager.FEATURE_USB_HOST) public boolean hasPermission(UsbDevice device) { if (mService == null) { return false; @@ -427,6 +514,7 @@ public class UsbManager { * @param accessory to check permissions for * @return true if caller has permission */ + @RequiresFeature(PackageManager.FEATURE_USB_ACCESSORY) public boolean hasPermission(UsbAccessory accessory) { if (mService == null) { return false; @@ -460,6 +548,7 @@ public class UsbManager { * @param device to request permissions for * @param pi PendingIntent for returning result */ + @RequiresFeature(PackageManager.FEATURE_USB_HOST) public void requestPermission(UsbDevice device, PendingIntent pi) { try { mService.requestDevicePermission(device, mContext.getPackageName(), pi); @@ -486,6 +575,7 @@ public class UsbManager { * @param accessory to request permissions for * @param pi PendingIntent for returning result */ + @RequiresFeature(PackageManager.FEATURE_USB_ACCESSORY) public void requestPermission(UsbAccessory accessory, PendingIntent pi) { try { mService.requestAccessoryPermission(accessory, mContext.getPackageName(), pi); @@ -548,15 +638,14 @@ public class UsbManager { * services offered by the device. * </p> * + * @deprecated use getCurrentFunctions() instead. * @param function name of the USB function * @return true if the USB function is enabled * * {@hide} */ + @Deprecated public boolean isFunctionEnabled(String function) { - if (mService == null) { - return false; - } try { return mService.isFunctionEnabled(function); } catch (RemoteException e) { @@ -565,7 +654,7 @@ public class UsbManager { } /** - * Sets the current USB function when in device mode. + * Sets the current USB functions when in device mode. * <p> * USB functions represent interfaces which are published to the host to access * services offered by the device. @@ -574,27 +663,59 @@ public class UsbManager { * automatically activate additional functions such as {@link #USB_FUNCTION_ADB} * or {@link #USB_FUNCTION_ACCESSORY} based on other settings and states. * </p><p> - * The allowed values are: {@link #USB_FUNCTION_NONE}, {@link #USB_FUNCTION_AUDIO_SOURCE}, - * {@link #USB_FUNCTION_MIDI}, {@link #USB_FUNCTION_MTP}, {@link #USB_FUNCTION_PTP}, - * or {@link #USB_FUNCTION_RNDIS}. - * </p><p> - * Also sets whether USB data (for example, MTP exposed pictures) should be made available - * on the USB connection when in device mode. Unlocking usb data should only be done with - * user involvement, since exposing pictures or other data could leak sensitive - * user information. + * An argument of 0 indicates that the device is charging, and can pick any + * appropriate function for that purpose. * </p><p> * Note: This function is asynchronous and may fail silently without applying * the requested changes. * </p> * - * @param function name of the USB function, or null to restore the default function - * @param usbDataUnlocked whether user data is accessible + * @param functions the USB function(s) to set, as a bitwise mask. + * Must satisfy {@link UsbManager#areSettableFunctions} * * {@hide} */ - public void setCurrentFunction(String function, boolean usbDataUnlocked) { + public void setCurrentFunctions(long functions) { try { - mService.setCurrentFunction(function, usbDataUnlocked); + mService.setCurrentFunctions(functions); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Sets the current USB functions when in device mode. + * + * @deprecated use setCurrentFunctions(long) instead. + * @param functions the USB function(s) to set. + * @param usbDataUnlocked unused + + * {@hide} + */ + @Deprecated + public void setCurrentFunction(String functions, boolean usbDataUnlocked) { + try { + mService.setCurrentFunction(functions, usbDataUnlocked); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the current USB functions in device mode. + * <p> + * This function returns the state of primary USB functions and can return a + * mask containing any usb function(s) except for ADB. + * </p> + * + * @return The currently enabled functions, in a bitwise mask. + * A zero mask indicates that the current function is the charging function. + * + * {@hide} + */ + public long getCurrentFunctions() { + try { + return mService.getCurrentFunctions(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -604,23 +725,37 @@ public class UsbManager { * Sets the screen unlocked functions, which are persisted and set as the current functions * whenever the screen is unlocked. * <p> - * The allowed values are: {@link #USB_FUNCTION_NONE}, - * {@link #USB_FUNCTION_MIDI}, {@link #USB_FUNCTION_MTP}, {@link #USB_FUNCTION_PTP}, - * or {@link #USB_FUNCTION_RNDIS}. - * {@link #USB_FUNCTION_NONE} has the effect of switching off this feature, so functions + * A zero mask has the effect of switching off this feature, so functions * no longer change on screen unlock. * </p><p> * Note: When the screen is on, this method will apply given functions as current functions, * which is asynchronous and may fail silently without applying the requested changes. * </p> * - * @param function function to set as default + * @param functions functions to set, in a bitwise mask. + * Must satisfy {@link UsbManager#areSettableFunctions} + * + * {@hide} + */ + public void setScreenUnlockedFunctions(long functions) { + try { + mService.setScreenUnlockedFunctions(functions); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Gets the current screen unlocked functions. + * + * @return The currently set screen enabled functions. + * A zero mask indicates that the screen unlocked functions feature is not enabled. * * {@hide} */ - public void setScreenUnlockedFunctions(String function) { + public long getScreenUnlockedFunctions() { try { - mService.setScreenUnlockedFunctions(function); + return mService.getScreenUnlockedFunctions(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -719,51 +854,71 @@ public class UsbManager { } } - /** @hide */ - public static String addFunction(String functions, String function) { - if (USB_FUNCTION_NONE.equals(functions)) { - return function; - } - if (!containsFunction(functions, function)) { - if (functions.length() > 0) { - functions += ","; - } - functions += function; - } - return functions; + /** + * Returns whether the given functions are valid inputs to UsbManager. + * Currently the empty functions or any of MTP, PTP, RNDIS, MIDI are accepted. + * + * @return Whether the mask is settable. + * + * {@hide} + */ + public static boolean areSettableFunctions(long functions) { + return functions == FUNCTION_NONE + || ((~SETTABLE_FUNCTIONS & functions) == 0 && Long.bitCount(functions) == 1); } - /** @hide */ - public static String removeFunction(String functions, String function) { - String[] split = functions.split(","); - for (int i = 0; i < split.length; i++) { - if (function.equals(split[i])) { - split[i] = null; - } + /** + * Converts the given function mask to string. Maintains ordering with respect to init scripts. + * + * @return String representation of given mask + * + * {@hide} + */ + public static String usbFunctionsToString(long functions) { + StringJoiner joiner = new StringJoiner(","); + if ((functions & FUNCTION_MTP) != 0) { + joiner.add(UsbManager.USB_FUNCTION_MTP); } - if (split.length == 1 && split[0] == null) { - return USB_FUNCTION_NONE; + if ((functions & FUNCTION_PTP) != 0) { + joiner.add(UsbManager.USB_FUNCTION_PTP); } - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < split.length; i++) { - String s = split[i]; - if (s != null) { - if (builder.length() > 0) { - builder.append(","); - } - builder.append(s); - } + if ((functions & FUNCTION_RNDIS) != 0) { + joiner.add(UsbManager.USB_FUNCTION_RNDIS); + } + if ((functions & FUNCTION_MIDI) != 0) { + joiner.add(UsbManager.USB_FUNCTION_MIDI); } - return builder.toString(); + if ((functions & FUNCTION_ACCESSORY) != 0) { + joiner.add(UsbManager.USB_FUNCTION_ACCESSORY); + } + if ((functions & FUNCTION_AUDIO_SOURCE) != 0) { + joiner.add(UsbManager.USB_FUNCTION_AUDIO_SOURCE); + } + if ((functions & FUNCTION_ADB) != 0) { + joiner.add(UsbManager.USB_FUNCTION_ADB); + } + return joiner.toString(); } - /** @hide */ - public static boolean containsFunction(String functions, String function) { - int index = functions.indexOf(function); - if (index < 0) return false; - if (index > 0 && functions.charAt(index - 1) != ',') return false; - int charAfter = index + function.length(); - if (charAfter < functions.length() && functions.charAt(charAfter) != ',') return false; - return true; + /** + * Parses a string of usb functions that are comma separated. + * + * @return A mask of all valid functions in the string + * + * {@hide} + */ + public static long usbFunctionsFromString(String functions) { + if (functions == null || functions.equals(USB_FUNCTION_NONE)) { + return FUNCTION_NONE; + } + long ret = 0; + for (String function : functions.split(",")) { + if (FUNCTION_NAME_TO_CODE.containsKey(function)) { + ret |= FUNCTION_NAME_TO_CODE.get(function); + } else if (function.length() > 0) { + throw new IllegalArgumentException("Invalid usb function " + functions); + } + } + return ret; } } diff --git a/android/hardware/usb/UsbRequest.java b/android/hardware/usb/UsbRequest.java index 2e8f8e12..f59c87ee 100644 --- a/android/hardware/usb/UsbRequest.java +++ b/android/hardware/usb/UsbRequest.java @@ -17,6 +17,7 @@ package android.hardware.usb; import android.annotation.Nullable; +import android.os.Build; import android.util.Log; import com.android.internal.util.Preconditions; @@ -43,7 +44,7 @@ public class UsbRequest { private static final String TAG = "UsbRequest"; // From drivers/usb/core/devio.c - private static final int MAX_USBFS_BUFFER_SIZE = 16384; + static final int MAX_USBFS_BUFFER_SIZE = 16384; // used by the JNI code private long mNativeContext; @@ -175,7 +176,9 @@ public class UsbRequest { * capacity will be ignored. Once the request * {@link UsbDeviceConnection#requestWait() is processed} the position will be set * to the number of bytes read/written. - * @param length number of bytes to read or write. + * @param length number of bytes to read or write. Before {@value Build.VERSION_CODES#P}, a + * value larger than 16384 bytes would be truncated down to 16384. In API + * {@value Build.VERSION_CODES#P} and after, any value of length is valid. * * @return true if the queueing operation succeeded * @@ -186,6 +189,11 @@ public class UsbRequest { boolean out = (mEndpoint.getDirection() == UsbConstants.USB_DIR_OUT); boolean result; + if (mConnection.getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P + && length > MAX_USBFS_BUFFER_SIZE) { + length = MAX_USBFS_BUFFER_SIZE; + } + synchronized (mLock) { // save our buffer for when the request has completed mBuffer = buffer; @@ -222,7 +230,10 @@ public class UsbRequest { * of the buffer is undefined until the request is returned by * {@link UsbDeviceConnection#requestWait}. If the request failed the buffer * will be unchanged; if the request succeeded the position of the buffer is - * incremented by the number of bytes sent/received. + * incremented by the number of bytes sent/received. Before + * {@value Build.VERSION_CODES#P}, a buffer of length larger than 16384 bytes + * would throw IllegalArgumentException. In API {@value Build.VERSION_CODES#P} + * and after, any size buffer is valid. * * @return true if the queueing operation succeeded */ @@ -244,9 +255,12 @@ public class UsbRequest { mIsUsingNewQueue = true; wasQueued = native_queue(null, 0, 0); } else { - // Can only send/receive MAX_USBFS_BUFFER_SIZE bytes at once - Preconditions.checkArgumentInRange(buffer.remaining(), 0, MAX_USBFS_BUFFER_SIZE, - "number of remaining bytes"); + if (mConnection.getContext().getApplicationInfo().targetSdkVersion + < Build.VERSION_CODES.P) { + // Can only send/receive MAX_USBFS_BUFFER_SIZE bytes at once + Preconditions.checkArgumentInRange(buffer.remaining(), 0, MAX_USBFS_BUFFER_SIZE, + "number of remaining bytes"); + } // Can not receive into read-only buffers. Preconditions.checkArgument(!(buffer.isReadOnly() && !isSend), "buffer can not be " |