Merge "media: add detachOutputSurface API for MediaCodec" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index 5d3cf72..4df7e3bd 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -22576,6 +22576,7 @@
method public void queueInputBuffer(int, int, int, long, int) throws android.media.MediaCodec.CryptoException;
method @FlaggedApi("com.android.media.codec.flags.large_audio_frame") public void queueInputBuffers(int, @NonNull java.util.ArrayDeque<android.media.MediaCodec.BufferInfo>);
method public void queueSecureInputBuffer(int, int, @NonNull android.media.MediaCodec.CryptoInfo, long, int) throws android.media.MediaCodec.CryptoException;
+ method @FlaggedApi("com.android.media.codec.flags.large_audio_frame") public void queueSecureInputBuffers(int, @NonNull java.util.ArrayDeque<android.media.MediaCodec.BufferInfo>, @NonNull java.util.ArrayDeque<android.media.MediaCodec.CryptoInfo>);
method public void release();
method public void releaseOutputBuffer(int, boolean);
method public void releaseOutputBuffer(int, long);
@@ -22752,6 +22753,7 @@
method @NonNull public android.media.MediaCodec.QueueRequest setIntegerParameter(@NonNull String, int);
method @NonNull public android.media.MediaCodec.QueueRequest setLinearBlock(@NonNull android.media.MediaCodec.LinearBlock, int, int);
method @NonNull public android.media.MediaCodec.QueueRequest setLongParameter(@NonNull String, long);
+ method @FlaggedApi("com.android.media.codec.flags.large_audio_frame") @NonNull public android.media.MediaCodec.QueueRequest setMultiFrameEncryptedLinearBlock(@NonNull android.media.MediaCodec.LinearBlock, @NonNull java.util.ArrayDeque<android.media.MediaCodec.BufferInfo>, @NonNull java.util.ArrayDeque<android.media.MediaCodec.CryptoInfo>);
method @NonNull public android.media.MediaCodec.QueueRequest setPresentationTimeUs(long);
method @NonNull public android.media.MediaCodec.QueueRequest setStringParameter(@NonNull String, @NonNull String);
}
diff --git a/core/tests/BroadcastRadioTests/Android.bp b/core/tests/BroadcastRadioTests/Android.bp
index 054d10c..39ea6e6 100644
--- a/core/tests/BroadcastRadioTests/Android.bp
+++ b/core/tests/BroadcastRadioTests/Android.bp
@@ -18,6 +18,7 @@
// all of the 'license_kinds' from "frameworks_base_license"
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["frameworks_base_license"],
}
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index d75c924..b9aee28 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -3250,6 +3250,51 @@
}
}
+ /**
+ * Similar to {@link #queueInputBuffers queueInputBuffers} but submits multiple access units
+ * in a buffer that is potentially encrypted.
+ * <strong>Check out further notes at {@link #queueInputBuffers queueInputBuffers}.</strong>
+ *
+ * @param index The index of a client-owned input buffer previously returned
+ * in a call to {@link #dequeueInputBuffer}.
+ * @param bufferInfos ArrayDeque of {@link MediaCodec.BufferInfo} that describes the
+ * contents in the buffer. The ArrayDeque and the BufferInfo objects provided
+ * can be recycled by the caller for re-use.
+ * @param cryptoInfos ArrayDeque of {@link MediaCodec.CryptoInfo} objects to facilitate the
+ * decryption of the contents. The ArrayDeque and the CryptoInfo objects
+ * provided can be reused immediately after the call returns. These objects
+ * should correspond to bufferInfo objects to ensure correct decryption.
+ * @throws IllegalStateException if not in the Executing state or not in asynchronous mode.
+ * @throws MediaCodec.CodecException upon codec error.
+ * @throws IllegalArgumentException upon if bufferInfos is empty, contains null, or if the
+ * access units are not contiguous.
+ * @throws CryptoException if an error occurs while attempting to decrypt the buffer.
+ * An error code associated with the exception helps identify the
+ * reason for the failure.
+ */
+ @FlaggedApi(FLAG_LARGE_AUDIO_FRAME)
+ public final void queueSecureInputBuffers(
+ int index,
+ @NonNull ArrayDeque<BufferInfo> bufferInfos,
+ @NonNull ArrayDeque<CryptoInfo> cryptoInfos) {
+ synchronized(mBufferLock) {
+ if (mBufferMode == BUFFER_MODE_BLOCK) {
+ throw new IncompatibleWithBlockModelException("queueSecureInputBuffers() "
+ + "is not compatible with CONFIGURE_FLAG_USE_BLOCK_MODEL. "
+ + "Please use getQueueRequest() to queue buffers");
+ }
+ invalidateByteBufferLocked(mCachedInputBuffers, index, true /* input */);
+ mDequeuedInputBuffers.remove(index);
+ }
+ try {
+ native_queueSecureInputBuffers(
+ index, bufferInfos.toArray(), cryptoInfos.toArray());
+ } catch (CryptoException | IllegalStateException | IllegalArgumentException e) {
+ revalidateByteBuffer(mCachedInputBuffers, index, true /* input */);
+ throw e;
+ }
+ }
+
private native final void native_queueSecureInputBuffer(
int index,
int offset,
@@ -3257,6 +3302,11 @@
long presentationTimeUs,
int flags) throws CryptoException;
+ private native final void native_queueSecureInputBuffers(
+ int index,
+ @NonNull Object[] bufferInfos,
+ @NonNull Object[] cryptoInfos) throws CryptoException, CodecException;
+
/**
* Returns the index of an input buffer to be filled with valid data
* or -1 if no such buffer is currently available.
@@ -3502,7 +3552,7 @@
mLinearBlock = block;
mOffset = offset;
mSize = size;
- mCryptoInfo = null;
+ mCryptoInfos.clear();
return this;
}
@@ -3536,7 +3586,44 @@
mLinearBlock = block;
mOffset = offset;
mSize = size;
- mCryptoInfo = cryptoInfo;
+ mCryptoInfos.clear();
+ mCryptoInfos.add(cryptoInfo);
+ return this;
+ }
+
+ /**
+ * Set an encrypted linear block to this queue request. Exactly one buffer must be
+ * set for a queue request before calling {@link #queue}. The block can contain multiple
+ * access units and if present should be laid out contiguously and without gaps.
+ *
+ * @param block The linear block object
+ * @param bufferInfos ArrayDeque of {@link MediaCodec.BufferInfo} that describes the
+ * contents in the buffer. The ArrayDeque and the BufferInfo objects
+ * provided can be recycled by the caller for re-use.
+ * @param cryptoInfos ArrayDeque of {@link MediaCodec.CryptoInfo} that describes the
+ * structure of the encrypted input samples. The ArrayDeque and the
+ * BufferInfo objects provided can be recycled by the caller for re-use.
+ * @return this object
+ * @throws IllegalStateException if a buffer is already set
+ * @throws IllegalArgumentException upon if bufferInfos is empty, contains null, or if the
+ * access units are not contiguous.
+ */
+ @FlaggedApi(FLAG_LARGE_AUDIO_FRAME)
+ public @NonNull QueueRequest setMultiFrameEncryptedLinearBlock(
+ @NonNull LinearBlock block,
+ @NonNull ArrayDeque<MediaCodec.BufferInfo> bufferInfos,
+ @NonNull ArrayDeque<MediaCodec.CryptoInfo> cryptoInfos) {
+ if (!isAccessible()) {
+ throw new IllegalStateException("The request is stale");
+ }
+ if (mLinearBlock != null || mHardwareBuffer != null) {
+ throw new IllegalStateException("Cannot set block twice");
+ }
+ mLinearBlock = block;
+ mBufferInfos.clear();
+ mBufferInfos.addAll(bufferInfos);
+ mCryptoInfos.clear();
+ mCryptoInfos.addAll(cryptoInfos);
return this;
}
@@ -3746,8 +3833,10 @@
mBufferInfos.add(info);
}
if (mLinearBlock != null) {
+
mCodec.native_queueLinearBlock(
- mIndex, mLinearBlock, mCryptoInfo,
+ mIndex, mLinearBlock,
+ mCryptoInfos.isEmpty() ? null : mCryptoInfos.toArray(),
mBufferInfos.toArray(),
mTuningKeys, mTuningValues);
} else if (mHardwareBuffer != null) {
@@ -3762,11 +3851,11 @@
mLinearBlock = null;
mOffset = 0;
mSize = 0;
- mCryptoInfo = null;
mHardwareBuffer = null;
mPresentationTimeUs = 0;
mFlags = 0;
mBufferInfos.clear();
+ mCryptoInfos.clear();
mTuningKeys.clear();
mTuningValues.clear();
return this;
@@ -3786,11 +3875,11 @@
private LinearBlock mLinearBlock = null;
private int mOffset = 0;
private int mSize = 0;
- private MediaCodec.CryptoInfo mCryptoInfo = null;
private HardwareBuffer mHardwareBuffer = null;
private long mPresentationTimeUs = 0;
private @BufferFlag int mFlags = 0;
private final ArrayDeque<BufferInfo> mBufferInfos = new ArrayDeque<>();
+ private final ArrayDeque<CryptoInfo> mCryptoInfos = new ArrayDeque<>();
private final ArrayList<String> mTuningKeys = new ArrayList<>();
private final ArrayList<Object> mTuningValues = new ArrayList<>();
@@ -3800,7 +3889,7 @@
private native void native_queueLinearBlock(
int index,
@NonNull LinearBlock block,
- @Nullable CryptoInfo cryptoInfo,
+ @Nullable Object[] cryptoInfos,
@NonNull Object[] bufferInfos,
@NonNull ArrayList<String> keys,
@NonNull ArrayList<Object> values);
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 8cdd59e..8396005 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -458,6 +458,24 @@
presentationTimeUs, flags, errorDetailMsg);
}
+status_t JMediaCodec::queueSecureInputBuffers(
+ size_t index,
+ size_t offset,
+ size_t size,
+ const sp<RefBase> &auInfos_,
+ const sp<RefBase> &cryptoInfos_,
+ AString *errorDetailMsg) {
+ sp<BufferInfosWrapper> auInfos((BufferInfosWrapper *)auInfos_.get());
+ sp<CryptoInfosWrapper> cryptoInfos((CryptoInfosWrapper *)cryptoInfos_.get());
+ return mCodec->queueSecureInputBuffers(
+ index,
+ offset,
+ size,
+ auInfos,
+ cryptoInfos,
+ errorDetailMsg);
+}
+
status_t JMediaCodec::queueBuffer(
size_t index, const std::shared_ptr<C2Buffer> &buffer,
const sp<RefBase> &infos, const sp<AMessage> &tunings, AString *errorDetailMsg) {
@@ -470,19 +488,16 @@
size_t index,
const sp<hardware::HidlMemory> &buffer,
size_t offset,
- const CryptoPlugin::SubSample *subSamples,
- size_t numSubSamples,
- const uint8_t key[16],
- const uint8_t iv[16],
- CryptoPlugin::Mode mode,
- const CryptoPlugin::Pattern &pattern,
+ size_t size,
const sp<RefBase> &infos,
+ const sp<RefBase> &cryptoInfos_,
const sp<AMessage> &tunings,
AString *errorDetailMsg) {
sp<BufferInfosWrapper> auInfo((BufferInfosWrapper *)infos.get());
+ sp<CryptoInfosWrapper> cryptoInfos((CryptoInfosWrapper *)cryptoInfos_.get());
return mCodec->queueEncryptedBuffer(
- index, buffer, offset, subSamples, numSubSamples, key, iv, mode, pattern,
- auInfo, tunings, errorDetailMsg);
+ index, buffer, offset, size, auInfo, cryptoInfos,
+ tunings, errorDetailMsg);
}
status_t JMediaCodec::dequeueInputBuffer(size_t *index, int64_t timeoutUs) {
@@ -2262,6 +2277,61 @@
CryptoPlugin::Pattern mPattern;
};
+// This class takes away all dependencies on java(env and jni) and
+// could be used for taking cryptoInfo objects to MediaCodec.
+struct MediaCodecCryptoInfo: public CodecCryptoInfo {
+ explicit MediaCodecCryptoInfo(const NativeCryptoInfo &cryptoInfo) {
+ if (cryptoInfo.mErr == OK) {
+ mNumSubSamples = cryptoInfo.mNumSubSamples;
+ mMode = cryptoInfo.mMode;
+ mPattern = cryptoInfo.mPattern;
+ if (cryptoInfo.mKey != nullptr) {
+ mKeyBuffer = ABuffer::CreateAsCopy(cryptoInfo.mKey, 16);
+ mKey = (uint8_t*)(mKeyBuffer.get() != nullptr ? mKeyBuffer.get()->data() : nullptr);
+ }
+ if (cryptoInfo.mIv != nullptr) {
+ mIvBuffer = ABuffer::CreateAsCopy(cryptoInfo.mIv, 16);
+ mIv = (uint8_t*)(mIvBuffer.get() != nullptr ? mIvBuffer.get()->data() : nullptr);
+ }
+ if (cryptoInfo.mSubSamples != nullptr) {
+ mSubSamplesBuffer = new ABuffer(sizeof(CryptoPlugin::SubSample) * mNumSubSamples);
+ if (mSubSamplesBuffer.get()) {
+ CryptoPlugin::SubSample * samples =
+ (CryptoPlugin::SubSample *)(mSubSamplesBuffer.get()->data());
+ for (int s = 0 ; s < mNumSubSamples ; s++) {
+ samples[s].mNumBytesOfClearData =
+ cryptoInfo.mSubSamples[s].mNumBytesOfClearData;
+ samples[s].mNumBytesOfEncryptedData =
+ cryptoInfo.mSubSamples[s].mNumBytesOfEncryptedData;
+ }
+ mSubSamples = (CryptoPlugin::SubSample *)mSubSamplesBuffer.get()->data();
+ }
+ }
+
+ }
+ }
+
+ explicit MediaCodecCryptoInfo(jint size) {
+ mSubSamplesBuffer = new ABuffer(sizeof(CryptoPlugin::SubSample) * 1);
+ mNumSubSamples = 1;
+ if (mSubSamplesBuffer.get()) {
+ CryptoPlugin::SubSample * samples =
+ (CryptoPlugin::SubSample *)(mSubSamplesBuffer.get()->data());
+ samples[0].mNumBytesOfClearData = size;
+ samples[0].mNumBytesOfEncryptedData = 0;
+ mSubSamples = (CryptoPlugin::SubSample *)mSubSamplesBuffer.get()->data();
+ }
+ }
+ ~MediaCodecCryptoInfo() {}
+
+protected:
+ // all backup buffers for the base object.
+ sp<ABuffer> mKeyBuffer;
+ sp<ABuffer> mIvBuffer;
+ sp<ABuffer> mSubSamplesBuffer;
+
+};
+
static void android_media_MediaCodec_queueSecureInputBuffer(
JNIEnv *env,
jobject thiz,
@@ -2430,6 +2500,99 @@
codec->getExceptionMessage(errorDetailMsg.c_str()).c_str(), codec->getCrypto());
}
+static status_t extractCryptoInfosFromObjectArray(JNIEnv * const env,
+ jint * const totalSize,
+ std::vector<std::unique_ptr<CodecCryptoInfo>> * const cryptoInfoObjs,
+ const jobjectArray &objArray,
+ AString * const errorDetailMsg) {
+ if (env == nullptr
+ || cryptoInfoObjs == nullptr
+ || totalSize == nullptr) {
+ if (errorDetailMsg) {
+ *errorDetailMsg = "Error: Null Parameters provided for extracting CryptoInfo";
+ }
+ return BAD_VALUE;
+ }
+ const jsize numEntries = env->GetArrayLength(objArray);
+ if (numEntries <= 0) {
+ if (errorDetailMsg) {
+ *errorDetailMsg = "Error: No CryptoInfo found while queuing for large frame input";
+ }
+ return BAD_VALUE;
+ }
+ cryptoInfoObjs->clear();
+ *totalSize = 0;
+ jint size = 0;
+ for (jsize i = 0; i < numEntries ; i++) {
+ jobject param = env->GetObjectArrayElement(objArray, i);
+ if (param == NULL) {
+ if (errorDetailMsg) {
+ *errorDetailMsg = "Error: Null Parameters provided for extracting CryptoInfo";
+ }
+ return BAD_VALUE;
+ }
+ NativeCryptoInfo nativeInfo(env, param);
+ std::unique_ptr<CodecCryptoInfo> info(new MediaCodecCryptoInfo(nativeInfo));
+ for (int i = 0; i < info->mNumSubSamples; i++) {
+ size += info->mSubSamples[i].mNumBytesOfClearData;
+ size += info->mSubSamples[i].mNumBytesOfEncryptedData;
+ }
+ cryptoInfoObjs->push_back(std::move(info));
+ }
+ *totalSize = size;
+ return OK;
+}
+
+
+static void android_media_MediaCodec_queueSecureInputBuffers(
+ JNIEnv *env,
+ jobject thiz,
+ jint index,
+ jobjectArray bufferInfosObjs,
+ jobjectArray cryptoInfoObjs) {
+ ALOGV("android_media_MediaCodec_queueSecureInputBuffers");
+
+ sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+ if (codec == NULL || codec->initCheck() != OK) {
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
+ return;
+ }
+ sp<BufferInfosWrapper> auInfos =
+ new BufferInfosWrapper{decltype(auInfos->value)()};
+ sp<CryptoInfosWrapper> cryptoInfos =
+ new CryptoInfosWrapper{decltype(cryptoInfos->value)()};
+ AString errorDetailMsg;
+ jint initialOffset = 0;
+ jint totalSize = 0;
+ status_t err = extractInfosFromObject(
+ env,
+ &initialOffset,
+ &totalSize,
+ &auInfos->value,
+ bufferInfosObjs,
+ &errorDetailMsg);
+ if (err == OK) {
+ err = extractCryptoInfosFromObjectArray(env,
+ &totalSize,
+ &cryptoInfos->value,
+ cryptoInfoObjs,
+ &errorDetailMsg);
+ }
+ if (err == OK) {
+ err = codec->queueSecureInputBuffers(
+ index,
+ initialOffset,
+ totalSize,
+ auInfos,
+ cryptoInfos,
+ &errorDetailMsg);
+ }
+ throwExceptionAsNecessary(
+ env, err, ACTION_CODE_FATAL,
+ codec->getExceptionMessage(errorDetailMsg.c_str()).c_str(), codec->getCrypto());
+}
+
static jobject android_media_MediaCodec_mapHardwareBuffer(JNIEnv *env, jclass, jobject bufferObj) {
ALOGV("android_media_MediaCodec_mapHardwareBuffer");
AHardwareBuffer *hardwareBuffer = android_hardware_HardwareBuffer_getNativeHardwareBuffer(
@@ -2762,7 +2925,7 @@
static void android_media_MediaCodec_native_queueLinearBlock(
JNIEnv *env, jobject thiz, jint index, jobject bufferObj,
- jobject cryptoInfoObj, jobjectArray objArray, jobject keys, jobject values) {
+ jobjectArray cryptoInfoArray, jobjectArray objArray, jobject keys, jobject values) {
ALOGV("android_media_MediaCodec_native_queueLinearBlock");
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
@@ -2780,8 +2943,8 @@
"error occurred while converting tunings from Java to native");
return;
}
- jint totalSize;
- jint initialOffset;
+ jint totalSize = 0;
+ jint initialOffset = 0;
std::vector<AccessUnitInfo> infoVec;
AString errorDetailMsg;
err = extractInfosFromObject(env,
@@ -2832,8 +2995,19 @@
"MediaCodec.LinearBlock#obtain method to obtain a compatible buffer.");
return;
}
- auto cryptoInfo =
- cryptoInfoObj ? NativeCryptoInfo{env, cryptoInfoObj} : NativeCryptoInfo{totalSize};
+ sp<CryptoInfosWrapper> cryptoInfos = new CryptoInfosWrapper{decltype(cryptoInfos->value)()};
+ jint sampleSize = 0;
+ if (cryptoInfoArray != nullptr) {
+ extractCryptoInfosFromObjectArray(env,
+ &sampleSize,
+ &cryptoInfos->value,
+ cryptoInfoArray,
+ &errorDetailMsg);
+ } else {
+ sampleSize = totalSize;
+ std::unique_ptr<CodecCryptoInfo> cryptoInfo{new MediaCodecCryptoInfo(totalSize)};
+ cryptoInfos->value.push_back(std::move(cryptoInfo));
+ }
if (env->ExceptionCheck()) {
// Creation of cryptoInfo failed. Let the exception bubble up.
return;
@@ -2842,11 +3016,9 @@
index,
memory,
initialOffset,
- cryptoInfo.mSubSamples, cryptoInfo.mNumSubSamples,
- (const uint8_t *)cryptoInfo.mKey, (const uint8_t *)cryptoInfo.mIv,
- cryptoInfo.mMode,
- cryptoInfo.mPattern,
+ sampleSize,
infos,
+ cryptoInfos,
tunings,
&errorDetailMsg);
ALOGI_IF(err != OK, "queueEncryptedLinearBlock returned err = %d", err);
@@ -3950,6 +4122,9 @@
{ "native_queueSecureInputBuffer", "(IILandroid/media/MediaCodec$CryptoInfo;JI)V",
(void *)android_media_MediaCodec_queueSecureInputBuffer },
+ { "native_queueSecureInputBuffers", "(I[Ljava/lang/Object;[Ljava/lang/Object;)V",
+ (void *)android_media_MediaCodec_queueSecureInputBuffers },
+
{ "native_mapHardwareBuffer",
"(Landroid/hardware/HardwareBuffer;)Landroid/media/Image;",
(void *)android_media_MediaCodec_mapHardwareBuffer },
@@ -3957,7 +4132,7 @@
{ "native_closeMediaImage", "(J)V", (void *)android_media_MediaCodec_closeMediaImage },
{ "native_queueLinearBlock",
- "(ILandroid/media/MediaCodec$LinearBlock;Landroid/media/MediaCodec$CryptoInfo;"
+ "(ILandroid/media/MediaCodec$LinearBlock;[Ljava/lang/Object;"
"[Ljava/lang/Object;Ljava/util/ArrayList;Ljava/util/ArrayList;)V",
(void *)android_media_MediaCodec_native_queueLinearBlock },
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index 02708ef..abb23f5 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -114,6 +114,14 @@
uint32_t flags,
AString *errorDetailMsg);
+ status_t queueSecureInputBuffers(
+ size_t index,
+ size_t offset,
+ size_t size,
+ const sp<RefBase> &auInfos,
+ const sp<RefBase> &cryptoInfos,
+ AString *errorDetailMsg);
+
status_t queueBuffer(
size_t index, const std::shared_ptr<C2Buffer> &buffer,
const sp<RefBase> &infos, const sp<AMessage> &tunings,
@@ -123,13 +131,9 @@
size_t index,
const sp<hardware::HidlMemory> &buffer,
size_t offset,
- const CryptoPlugin::SubSample *subSamples,
- size_t numSubSamples,
- const uint8_t key[16],
- const uint8_t iv[16],
- CryptoPlugin::Mode mode,
- const CryptoPlugin::Pattern &pattern,
+ size_t size,
const sp<RefBase> &infos,
+ const sp<RefBase> &cryptoInfos,
const sp<AMessage> &tunings,
AString *errorDetailMsg);
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index d5b3c7d..ece8851 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -14,12 +14,20 @@
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOnSupported();
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isTagIntentAppPreferenceSupported();
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
+ method @FlaggedApi("android.nfc.nfc_vendor_cmd") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void registerNfcVendorNciCallback(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.NfcVendorNciCallback);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler);
+ method @FlaggedApi("android.nfc.nfc_vendor_cmd") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int sendVendorNciMessage(int, @IntRange(from=0, to=15) int, @IntRange(from=0) int, @NonNull byte[]);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean);
method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setReaderMode(boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int setTagIntentAppPreferenceForUser(int, @NonNull String, boolean);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void unregisterNfcVendorNciCallback(@NonNull android.nfc.NfcAdapter.NfcVendorNciCallback);
field @FlaggedApi("android.nfc.enable_nfc_mainline") public static final String ACTION_REQUIRE_UNLOCK_FOR_NFC = "android.nfc.action.REQUIRE_UNLOCK_FOR_NFC";
+ field @FlaggedApi("android.nfc.nfc_vendor_cmd") public static final int MESSAGE_TYPE_COMMAND = 1; // 0x1
+ field @FlaggedApi("android.nfc.nfc_vendor_cmd") public static final int SEND_VENDOR_NCI_STATUS_FAILED = 3; // 0x3
+ field @FlaggedApi("android.nfc.nfc_vendor_cmd") public static final int SEND_VENDOR_NCI_STATUS_MESSAGE_CORRUPTED = 2; // 0x2
+ field @FlaggedApi("android.nfc.nfc_vendor_cmd") public static final int SEND_VENDOR_NCI_STATUS_REJECTED = 1; // 0x1
+ field @FlaggedApi("android.nfc.nfc_vendor_cmd") public static final int SEND_VENDOR_NCI_STATUS_SUCCESS = 0; // 0x0
field public static final int TAG_INTENT_APP_PREF_RESULT_PACKAGE_NOT_FOUND = -1; // 0xffffffff
field public static final int TAG_INTENT_APP_PREF_RESULT_SUCCESS = 0; // 0x0
field public static final int TAG_INTENT_APP_PREF_RESULT_UNAVAILABLE = -2; // 0xfffffffe
@@ -33,6 +41,11 @@
method public boolean onUnlockAttempted(android.nfc.Tag);
}
+ @FlaggedApi("android.nfc.nfc_vendor_cmd") public static interface NfcAdapter.NfcVendorNciCallback {
+ method @FlaggedApi("android.nfc.nfc_vendor_cmd") public void onVendorNciNotification(@IntRange(from=9, to=15) int, int, @NonNull byte[]);
+ method @FlaggedApi("android.nfc.nfc_vendor_cmd") public void onVendorNciResponse(@IntRange(from=0, to=15) int, int, @NonNull byte[]);
+ }
+
}
package android.nfc.cardemulation {
diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
index 85879ac..8fea5af 100644
--- a/nfc/java/android/nfc/INfcAdapter.aidl
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -24,6 +24,7 @@
import android.nfc.IAppCallback;
import android.nfc.INfcAdapterExtras;
import android.nfc.INfcControllerAlwaysOnListener;
+import android.nfc.INfcVendorNciCallback;
import android.nfc.INfcTag;
import android.nfc.INfcCardEmulation;
import android.nfc.INfcFCardEmulation;
@@ -87,4 +88,7 @@
boolean isObserveModeSupported();
boolean setObserveMode(boolean enabled);
void updateDiscoveryTechnology(IBinder b, int pollFlags, int listenFlags);
+ int sendVendorNciMessage(int mt, int gid, int oid, in byte[] payload);
+ void registerVendorExtensionCallback(in INfcVendorNciCallback callbacks);
+ void unregisterVendorExtensionCallback(in INfcVendorNciCallback callbacks);
}
diff --git a/nfc/java/android/nfc/INfcVendorNciCallback.aidl b/nfc/java/android/nfc/INfcVendorNciCallback.aidl
new file mode 100644
index 0000000..821dc6f
--- /dev/null
+++ b/nfc/java/android/nfc/INfcVendorNciCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 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.nfc;
+
+/**
+ * @hide
+ */
+oneway interface INfcVendorNciCallback {
+ void onVendorResponseReceived(int gid, int oid, in byte[] payload);
+ void onVendorNotificationReceived(int gid, int oid, in byte[] payload);
+}
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 0791721..40bbe74 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -19,6 +19,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -75,6 +76,7 @@
static final String TAG = "NFC";
private final NfcControllerAlwaysOnListener mControllerAlwaysOnListener;
+ private final NfcVendorNciCallbackListener mNfcVendorNciCallbackListener;
/**
* Intent to start an activity when a tag with NDEF payload is discovered.
@@ -861,6 +863,7 @@
mTagRemovedListener = null;
mLock = new Object();
mControllerAlwaysOnListener = new NfcControllerAlwaysOnListener(getService());
+ mNfcVendorNciCallbackListener = new NfcVendorNciCallbackListener(getService());
}
/**
@@ -2757,4 +2760,163 @@
return false;
}
}
+
+ /**
+ * Vendor NCI command success.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
+ public static final int SEND_VENDOR_NCI_STATUS_SUCCESS = 0;
+ /**
+ * Vendor NCI command rejected.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
+ public static final int SEND_VENDOR_NCI_STATUS_REJECTED = 1;
+ /**
+ * Vendor NCI command corrupted.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
+ public static final int SEND_VENDOR_NCI_STATUS_MESSAGE_CORRUPTED = 2;
+ /**
+ * Vendor NCI command failed with unknown reason.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
+ public static final int SEND_VENDOR_NCI_STATUS_FAILED = 3;
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ SEND_VENDOR_NCI_STATUS_SUCCESS,
+ SEND_VENDOR_NCI_STATUS_REJECTED,
+ SEND_VENDOR_NCI_STATUS_MESSAGE_CORRUPTED,
+ SEND_VENDOR_NCI_STATUS_FAILED,
+ })
+ @interface SendVendorNciStatus {}
+
+ /**
+ * Message Type for NCI Command.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
+ public static final int MESSAGE_TYPE_COMMAND = 1;
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ MESSAGE_TYPE_COMMAND,
+ })
+ @interface MessageType {}
+
+ /**
+ * Send Vendor specific Nci Messages with custom message type.
+ *
+ * The format of the NCI messages are defined in the NCI specification. The platform is
+ * responsible for fragmenting the payload if necessary.
+ *
+ * Note that mt (message type) is added at the beginning of method parameters as it is more
+ * distinctive than other parameters and was requested from vendor.
+ *
+ * @param mt message Type of the command
+ * @param gid group ID of the command. This needs to be one of the vendor reserved GIDs from
+ * the NCI specification
+ * @param oid opcode ID of the command. This is left to the OEM / vendor to decide
+ * @param payload containing vendor Nci message payload
+ * @return message send status
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ public @SendVendorNciStatus int sendVendorNciMessage(@MessageType int mt,
+ @IntRange(from = 0, to = 15) int gid, @IntRange(from = 0) int oid,
+ @NonNull byte[] payload) {
+ Objects.requireNonNull(payload, "Payload must not be null");
+ try {
+ return sService.sendVendorNciMessage(mt, gid, oid, payload);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Register an {@link NfcVendorNciCallback} to listen for Nfc vendor responses and notifications
+ * <p>The provided callback will be invoked by the given {@link Executor}.
+ *
+ * <p>When first registering a callback, the callbacks's
+ * {@link NfcVendorNciCallback#onVendorNciCallBack(byte[])} is immediately invoked to
+ * notify the vendor notification.
+ *
+ * @param executor an {@link Executor} to execute given callback
+ * @param callback user implementation of the {@link NfcVendorNciCallback}
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ public void registerNfcVendorNciCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull NfcVendorNciCallback callback) {
+ mNfcVendorNciCallbackListener.register(executor, callback);
+ }
+
+ /**
+ * Unregister the specified {@link NfcVendorNciCallback}
+ *
+ * <p>The same {@link NfcVendorNciCallback} object used when calling
+ * {@link #registerNfcVendorNciCallback(Executor, NfcVendorNciCallback)} must be used.
+ *
+ * <p>Callbacks are automatically unregistered when application process goes away
+ *
+ * @param callback user implementation of the {@link NfcVendorNciCallback}
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ public void unregisterNfcVendorNciCallback(@NonNull NfcVendorNciCallback callback) {
+ mNfcVendorNciCallbackListener.unregister(callback);
+ }
+
+ /**
+ * Interface for receiving vendor NCI responses and notifications.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
+ public interface NfcVendorNciCallback {
+ /**
+ * Invoked when a vendor specific NCI response is received.
+ *
+ * @param gid group ID of the command. This needs to be one of the vendor reserved GIDs from
+ * the NCI specification.
+ * @param oid opcode ID of the command. This is left to the OEM / vendor to decide.
+ * @param payload containing vendor Nci message payload.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
+ void onVendorNciResponse(
+ @IntRange(from = 0, to = 15) int gid, int oid, @NonNull byte[] payload);
+
+ /**
+ * Invoked when a vendor specific NCI notification is received.
+ *
+ * @param gid group ID of the command. This needs to be one of the vendor reserved GIDs from
+ * the NCI specification.
+ * @param oid opcode ID of the command. This is left to the OEM / vendor to decide.
+ * @param payload containing vendor Nci message payload.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
+ void onVendorNciNotification(
+ @IntRange(from = 9, to = 15) int gid, int oid, @NonNull byte[] payload);
+ }
}
diff --git a/nfc/java/android/nfc/NfcVendorNciCallbackListener.java b/nfc/java/android/nfc/NfcVendorNciCallbackListener.java
new file mode 100644
index 0000000..742d75f
--- /dev/null
+++ b/nfc/java/android/nfc/NfcVendorNciCallbackListener.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 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.nfc;
+
+import android.annotation.NonNull;
+import android.nfc.NfcAdapter.NfcVendorNciCallback;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * @hide
+ */
+public final class NfcVendorNciCallbackListener extends INfcVendorNciCallback.Stub {
+ private static final String TAG = "Nfc.NfcVendorNciCallbacks";
+ private final INfcAdapter mAdapter;
+ private boolean mIsRegistered = false;
+ private final Map<NfcVendorNciCallback, Executor> mCallbackMap = new HashMap<>();
+
+ public NfcVendorNciCallbackListener(@NonNull INfcAdapter adapter) {
+ mAdapter = adapter;
+ }
+
+ public void register(@NonNull Executor executor, @NonNull NfcVendorNciCallback callback) {
+ synchronized (this) {
+ if (mCallbackMap.containsKey(callback)) {
+ return;
+ }
+ mCallbackMap.put(callback, executor);
+ if (!mIsRegistered) {
+ try {
+ mAdapter.registerVendorExtensionCallback(this);
+ mIsRegistered = true;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to register adapter state callback");
+ mCallbackMap.remove(callback);
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+
+ public void unregister(@NonNull NfcVendorNciCallback callback) {
+ synchronized (this) {
+ if (!mCallbackMap.containsKey(callback) || !mIsRegistered) {
+ return;
+ }
+ if (mCallbackMap.size() == 1) {
+ try {
+ mAdapter.unregisterVendorExtensionCallback(this);
+ mIsRegistered = false;
+ mCallbackMap.remove(callback);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to unregister AdapterStateCallback with service");
+ throw e.rethrowFromSystemServer();
+ }
+ } else {
+ mCallbackMap.remove(callback);
+ }
+ }
+ }
+
+ @Override
+ public void onVendorResponseReceived(int gid, int oid, @NonNull byte[] payload)
+ throws RemoteException {
+ synchronized (this) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ for (NfcVendorNciCallback callback : mCallbackMap.keySet()) {
+ Executor executor = mCallbackMap.get(callback);
+ executor.execute(() -> callback.onVendorNciResponse(gid, oid, payload));
+ }
+ } catch (RuntimeException ex) {
+ throw ex;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @Override
+ public void onVendorNotificationReceived(int gid, int oid, @NonNull byte[] payload)
+ throws RemoteException {
+ synchronized (this) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ for (NfcVendorNciCallback callback : mCallbackMap.keySet()) {
+ Executor executor = mCallbackMap.get(callback);
+ executor.execute(() -> callback.onVendorNciNotification(gid, oid, payload));
+ }
+ } catch (RuntimeException ex) {
+ throw ex;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+}
diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig
index 11be905..cb1a542 100644
--- a/nfc/java/android/nfc/flags.aconfig
+++ b/nfc/java/android/nfc/flags.aconfig
@@ -62,3 +62,10 @@
description: "Flag for NFC set discovery tech API"
bug: "300351519"
}
+
+flag {
+ name: "nfc_vendor_cmd"
+ namespace: "nfc"
+ description: "Enable NFC vendor command support"
+ bug: "289879306"
+}
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index 7ff6d11..1420671 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -341,8 +341,10 @@
mHasAboveClient = false;
for (int i = mConnections.size() - 1; i >= 0; i--) {
ConnectionRecord cr = mConnections.valueAt(i);
- if (cr.binding.service.app.mServices != this
- && cr.hasFlag(Context.BIND_ABOVE_CLIENT)) {
+
+ final boolean isSameProcess = cr.binding.service.app != null
+ && cr.binding.service.app.mServices == this;
+ if (!isSameProcess && cr.hasFlag(Context.BIND_ABOVE_CLIENT)) {
mHasAboveClient = true;
break;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 0504344..732d0ce 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -2578,6 +2578,31 @@
assertTrue(CACHED_APP_MAX_ADJ >= app3.mState.getSetAdj());
}
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testUpdateOomAdj_DoOne_AboveClient_NotStarted() {
+ ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+ MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
+ doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
+ doReturn(app).when(sService).getTopApp();
+ sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
+
+ assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
+
+ // Start binding to a service that isn't running yet.
+ ServiceRecord sr = makeServiceRecord(app);
+ sr.app = null;
+ bindService(null, app, sr, Context.BIND_ABOVE_CLIENT, mock(IBinder.class));
+
+ // Since sr.app is null, this service cannot be in the same process as the
+ // client so we expect the BIND_ABOVE_CLIENT adjustment to take effect.
+ app.mServices.updateHasAboveClientLocked();
+ sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
+ assertTrue(app.mServices.hasAboveClient());
+ assertNotEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
+ }
+
private ProcessRecord makeDefaultProcessRecord(int pid, int uid, String processName,
String packageName, boolean hasShownUi) {
long now = SystemClock.uptimeMillis();