Merge "Remove call to getComponent(StatusBar) from PowerUI."
diff --git a/apex/statsd/.clang-format b/apex/statsd/.clang-format
new file mode 100644
index 0000000..cead3a0
--- /dev/null
+++ b/apex/statsd/.clang-format
@@ -0,0 +1,17 @@
+BasedOnStyle: Google
+AllowShortIfStatementsOnASingleLine: true
+AllowShortFunctionsOnASingleLine: false
+AllowShortLoopsOnASingleLine: true
+BinPackArguments: true
+BinPackParameters: true
+ColumnLimit: 100
+CommentPragmas: NOLINT:.*
+ContinuationIndentWidth: 8
+DerivePointerAlignment: false
+IndentWidth: 4
+PointerAlignment: Left
+TabWidth: 4
+AccessModifierOffset: -4
+IncludeCategories:
+ - Regex: '^"Log\.h"'
+ Priority: -1
diff --git a/api/current.txt b/api/current.txt
index 54d33a2..6d2221e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9490,6 +9490,7 @@
method @Nullable public android.database.Cursor query(@NonNull android.net.Uri, @Nullable String[], @Nullable String, @Nullable String[], @Nullable String, @Nullable android.os.CancellationSignal);
method @Nullable public android.database.Cursor query(@NonNull android.net.Uri, @Nullable String[], @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal);
method public boolean refresh(android.net.Uri, @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal);
+ method @NonNull public final android.content.Context requireContext();
method public final void restoreCallingIdentity(@NonNull android.content.ContentProvider.CallingIdentity);
method protected final void setPathPermissions(@Nullable android.content.pm.PathPermission[]);
method protected final void setReadPermission(@Nullable String);
@@ -30343,6 +30344,8 @@
method public int describeContents();
method public android.net.wifi.hotspot2.pps.Credential getCredential();
method public android.net.wifi.hotspot2.pps.HomeSp getHomeSp();
+ method public long getSubscriptionExpirationTimeInMillis();
+ method public boolean isOsuProvisioned();
method public void setCredential(android.net.wifi.hotspot2.pps.Credential);
method public void setHomeSp(android.net.wifi.hotspot2.pps.HomeSp);
method public void writeToParcel(android.os.Parcel, int);
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 02b6b3e..7de8793 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -834,6 +834,23 @@
}
/**
+ * Retrieves a Non-Nullable Context this provider is running in, this is intended to be called
+ * after {@link #onCreate}. When called before context was created, an IllegalStateException
+ * will be thrown.
+ * <p>
+ * Note A provider must be declared in the manifest and created automatically by the system,
+ * and context is only available after {@link #onCreate} is called.
+ */
+ @NonNull
+ public final Context requireContext() {
+ final Context ctx = getContext();
+ if (ctx == null) {
+ throw new IllegalStateException("Cannot find context from the provider.");
+ }
+ return ctx;
+ }
+
+ /**
* Set the calling package, returning the current value (or {@code null})
* which can be used later to restore the previous state.
*/
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index 91f3a20..79bec92 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -42,20 +42,23 @@
*/
interface ILocationManager
{
- Location getLastLocation(in LocationRequest request, String packageName);
+ Location getLastLocation(in LocationRequest request, String packageName, String featureId);
boolean getCurrentLocation(in LocationRequest request,
in ICancellationSignal cancellationSignal, in ILocationListener listener,
- String packageName, String listenerIdentifier);
+ String packageName, String featureId, String listenerIdentifier);
void requestLocationUpdates(in LocationRequest request, in ILocationListener listener,
- in PendingIntent intent, String packageName, String listenerIdentifier);
+ in PendingIntent intent, String packageName, String featureId,
+ String listenerIdentifier);
void removeUpdates(in ILocationListener listener, in PendingIntent intent, String packageName);
void requestGeofence(in LocationRequest request, in Geofence geofence,
- in PendingIntent intent, String packageName, String listenerIdentifier);
+ in PendingIntent intent, String packageName, String featureId,
+ String listenerIdentifier);
void removeGeofence(in Geofence fence, in PendingIntent intent, String packageName);
- boolean registerGnssStatusCallback(IGnssStatusListener callback, String packageName);
+ boolean registerGnssStatusCallback(IGnssStatusListener callback, String packageName,
+ String featureId);
void unregisterGnssStatusCallback(IGnssStatusListener callback);
boolean geocoderIsPresent();
@@ -69,14 +72,14 @@
boolean sendNiResponse(int notifId, int userResponse);
boolean addGnssMeasurementsListener(in IGnssMeasurementsListener listener,
- String packageName, String listenerIdentifier);
+ String packageName, String featureId, String listenerIdentifier);
void injectGnssMeasurementCorrections(in GnssMeasurementCorrections corrections,
in String packageName);
long getGnssCapabilities(in String packageName);
void removeGnssMeasurementsListener(in IGnssMeasurementsListener listener);
boolean addGnssNavigationMessageListener(in IGnssNavigationMessageListener listener,
- String packageName, String listenerIdentifier);
+ String packageName, String featureId, String listenerIdentifier);
void removeGnssNavigationMessageListener(in IGnssNavigationMessageListener listener);
int getGnssYearOfHardware();
@@ -84,7 +87,7 @@
int getGnssBatchSize(String packageName);
boolean addGnssBatchingCallback(in IBatchedLocationCallback callback, String packageName,
- String listenerIdentifier);
+ String featureId, String listenerIdentifier);
void removeGnssBatchingCallback();
boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName);
void flushGnssBatch(String packageName);
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 9e17e95..010f92f 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -515,7 +515,8 @@
@RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
public Location getLastLocation() {
try {
- return mService.getLastLocation(null, mContext.getPackageName());
+ return mService.getLastLocation(null, mContext.getPackageName(),
+ mContext.getFeatureId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -547,7 +548,8 @@
provider, 0, 0, true);
try {
- return mService.getLastLocation(request, mContext.getPackageName());
+ return mService.getLastLocation(request, mContext.getPackageName(),
+ mContext.getFeatureId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -641,7 +643,7 @@
try {
if (mService.getCurrentLocation(currentLocationRequest, remoteCancellationSignal,
- listenerTransport, mContext.getPackageName(),
+ listenerTransport, mContext.getPackageName(), mContext.getFeatureId(),
getListenerIdentifier(consumer))) {
listenerTransport.register(mContext.getSystemService(AlarmManager.class),
remoteCancellationSignal);
@@ -1085,7 +1087,8 @@
boolean registered = false;
try {
mService.requestLocationUpdates(locationRequest, transport, null,
- mContext.getPackageName(), getListenerIdentifier(listener));
+ mContext.getPackageName(), mContext.getFeatureId(),
+ getListenerIdentifier(listener));
registered = true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1130,7 +1133,8 @@
try {
mService.requestLocationUpdates(locationRequest, null, pendingIntent,
- mContext.getPackageName(), getListenerIdentifier(pendingIntent));
+ mContext.getPackageName(), mContext.getFeatureId(),
+ getListenerIdentifier(pendingIntent));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1594,7 +1598,7 @@
LocationRequest request = new LocationRequest().setExpireIn(expiration);
try {
mService.requestGeofence(request, fence, intent, mContext.getPackageName(),
- getListenerIdentifier(intent));
+ mContext.getFeatureId(), getListenerIdentifier(intent));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1672,7 +1676,7 @@
try {
mService.requestGeofence(request, fence, intent, mContext.getPackageName(),
- getListenerIdentifier(intent));
+ mContext.getFeatureId(), getListenerIdentifier(intent));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2748,7 +2752,7 @@
mListenerTransport = new GnssStatusListener();
return mService.registerGnssStatusCallback(mListenerTransport,
- mContext.getPackageName());
+ mContext.getPackageName(), mContext.getFeatureId());
}
@Override
@@ -2808,7 +2812,8 @@
mListenerTransport = new GnssMeasurementsListener();
return mService.addGnssMeasurementsListener(mListenerTransport,
- mContext.getPackageName(), "gnss measurement callback");
+ mContext.getPackageName(), mContext.getFeatureId(),
+ "gnss measurement callback");
}
@Override
@@ -2844,7 +2849,8 @@
mListenerTransport = new GnssNavigationMessageListener();
return mService.addGnssNavigationMessageListener(mListenerTransport,
- mContext.getPackageName(), "gnss navigation callback");
+ mContext.getPackageName(), mContext.getFeatureId(),
+ "gnss navigation callback");
}
@Override
@@ -2880,7 +2886,7 @@
mListenerTransport = new BatchedLocationCallback();
return mService.addGnssBatchingCallback(mListenerTransport, mContext.getPackageName(),
- "batched location callback");
+ mContext.getFeatureId(), "batched location callback");
}
@Override
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 7906fa3..7d107dd 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -89,6 +89,10 @@
* of audio/video files and streams. An example on how to use the methods in
* this class can be found in {@link android.widget.VideoView}.
*
+ * <p>MediaPlayer is not thread-safe. Creation of and all access to player instances
+ * should be on the same thread. If registering <a href="#Callbacks">callbacks</a>,
+ * the thread must have a Looper.
+ *
* <p>Topics covered here are:
* <ol>
* <li><a href="#StateDiagram">State Diagram</a>
@@ -305,7 +309,7 @@
* </li>
* <li>When the playback reaches the end of stream, the playback completes.
* <ul>
- * <li>If the looping mode was being set to <var>true</var>with
+ * <li>If the looping mode was being set to <var>true</var> with
* {@link #setLooping(boolean)}, the MediaPlayer object shall remain in
* the <em>Started</em> state.</li>
* <li>If the looping mode was set to <var>false
@@ -554,13 +558,13 @@
* possible runtime errors during playback or streaming. Registration for
* these events is done by properly setting the appropriate listeners (via calls
* to
- * {@link #setOnPreparedListener(OnPreparedListener)}setOnPreparedListener,
- * {@link #setOnVideoSizeChangedListener(OnVideoSizeChangedListener)}setOnVideoSizeChangedListener,
- * {@link #setOnSeekCompleteListener(OnSeekCompleteListener)}setOnSeekCompleteListener,
- * {@link #setOnCompletionListener(OnCompletionListener)}setOnCompletionListener,
- * {@link #setOnBufferingUpdateListener(OnBufferingUpdateListener)}setOnBufferingUpdateListener,
- * {@link #setOnInfoListener(OnInfoListener)}setOnInfoListener,
- * {@link #setOnErrorListener(OnErrorListener)}setOnErrorListener, etc).
+ * {@link #setOnPreparedListener(OnPreparedListener) setOnPreparedListener},
+ * {@link #setOnVideoSizeChangedListener(OnVideoSizeChangedListener) setOnVideoSizeChangedListener},
+ * {@link #setOnSeekCompleteListener(OnSeekCompleteListener) setOnSeekCompleteListener},
+ * {@link #setOnCompletionListener(OnCompletionListener) setOnCompletionListener},
+ * {@link #setOnBufferingUpdateListener(OnBufferingUpdateListener) setOnBufferingUpdateListener},
+ * {@link #setOnInfoListener(OnInfoListener) setOnInfoListener},
+ * {@link #setOnErrorListener(OnErrorListener) setOnErrorListener}, etc).
* In order to receive the respective callback
* associated with these listeners, applications are required to create
* MediaPlayer objects on a thread with its own Looper running (main UI
diff --git a/media/jni/soundpool/Android.bp b/media/jni/soundpool/Android.bp
index 35b7b01..e1945dd 100644
--- a/media/jni/soundpool/Android.bp
+++ b/media/jni/soundpool/Android.bp
@@ -3,11 +3,16 @@
srcs: [
"android_media_SoundPool.cpp",
+ "Sound.cpp",
+ "SoundDecoder.cpp",
+ "SoundManager.cpp",
"SoundPool.cpp",
- "SoundPoolThread.cpp",
+ "Stream.cpp",
+ "StreamManager.cpp",
],
shared_libs: [
+ "libaudioutils",
"liblog",
"libcutils",
"libutils",
diff --git a/media/jni/soundpool/Sound.cpp b/media/jni/soundpool/Sound.cpp
new file mode 100644
index 0000000..0bbc3e4
--- /dev/null
+++ b/media/jni/soundpool/Sound.cpp
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SoundPool::Sound"
+#include <utils/Log.h>
+
+#include "Sound.h"
+
+#include <media/NdkMediaCodec.h>
+#include <media/NdkMediaExtractor.h>
+#include <media/NdkMediaFormat.h>
+
+namespace android::soundpool {
+
+constexpr uint32_t kMaxSampleRate = 192000;
+constexpr size_t kDefaultHeapSize = 1024 * 1024; // 1MB (compatible with low mem devices)
+
+Sound::Sound(int32_t soundID, int fd, int64_t offset, int64_t length)
+ : mSoundID(soundID)
+ , mFd(dup(fd))
+ , mOffset(offset)
+ , mLength(length)
+{
+ ALOGV("%s(soundID=%d, fd=%d, offset=%lld, length=%lld)",
+ __func__, soundID, fd, (long long)offset, (long long)length);
+ ALOGW_IF(mFd == -1, "Unable to dup descriptor %d", fd);
+}
+
+Sound::~Sound()
+{
+ ALOGV("%s(soundID=%d, fd=%d)", __func__, mSoundID, mFd.get());
+}
+
+static status_t decode(int fd, int64_t offset, int64_t length,
+ uint32_t *rate, int32_t *channelCount, audio_format_t *audioFormat,
+ audio_channel_mask_t *channelMask, sp<MemoryHeapBase> heap,
+ size_t *sizeInBytes) {
+ ALOGV("%s(fd=%d, offset=%lld, length=%lld, ...)",
+ __func__, fd, (long long)offset, (long long)length);
+ std::unique_ptr<AMediaExtractor, decltype(&AMediaExtractor_delete)> ex{
+ AMediaExtractor_new(), &AMediaExtractor_delete};
+ status_t err = AMediaExtractor_setDataSourceFd(ex.get(), fd, offset, length);
+
+ if (err != AMEDIA_OK) {
+ return err;
+ }
+
+ *audioFormat = AUDIO_FORMAT_PCM_16_BIT; // default format for audio codecs.
+ const size_t numTracks = AMediaExtractor_getTrackCount(ex.get());
+ for (size_t i = 0; i < numTracks; i++) {
+ std::unique_ptr<AMediaFormat, decltype(&AMediaFormat_delete)> format{
+ AMediaExtractor_getTrackFormat(ex.get(), i), &AMediaFormat_delete};
+ const char *mime;
+ if (!AMediaFormat_getString(format.get(), AMEDIAFORMAT_KEY_MIME, &mime)) {
+ return UNKNOWN_ERROR;
+ }
+ if (strncmp(mime, "audio/", 6) == 0) {
+ std::unique_ptr<AMediaCodec, decltype(&AMediaCodec_delete)> codec{
+ AMediaCodec_createDecoderByType(mime), &AMediaCodec_delete};
+ if (codec == nullptr
+ || AMediaCodec_configure(codec.get(), format.get(),
+ nullptr /* window */, nullptr /* drm */, 0 /* flags */) != AMEDIA_OK
+ || AMediaCodec_start(codec.get()) != AMEDIA_OK
+ || AMediaExtractor_selectTrack(ex.get(), i) != AMEDIA_OK) {
+ return UNKNOWN_ERROR;
+ }
+
+ bool sawInputEOS = false;
+ bool sawOutputEOS = false;
+ uint8_t* writePos = static_cast<uint8_t*>(heap->getBase());
+ size_t available = heap->getSize();
+ size_t written = 0;
+ format.reset(AMediaCodec_getOutputFormat(codec.get())); // update format.
+
+ while (!sawOutputEOS) {
+ if (!sawInputEOS) {
+ ssize_t bufidx = AMediaCodec_dequeueInputBuffer(codec.get(), 5000);
+ ALOGV("%s: input buffer %zd", __func__, bufidx);
+ if (bufidx >= 0) {
+ size_t bufsize;
+ uint8_t * const buf = AMediaCodec_getInputBuffer(
+ codec.get(), bufidx, &bufsize);
+ if (buf == nullptr) {
+ ALOGE("%s: AMediaCodec_getInputBuffer returned nullptr, short decode",
+ __func__);
+ break;
+ }
+ int sampleSize = AMediaExtractor_readSampleData(ex.get(), buf, bufsize);
+ ALOGV("%s: read %d", __func__, sampleSize);
+ if (sampleSize < 0) {
+ sampleSize = 0;
+ sawInputEOS = true;
+ ALOGV("%s: EOS", __func__);
+ }
+ const int64_t presentationTimeUs = AMediaExtractor_getSampleTime(ex.get());
+
+ const media_status_t mstatus = AMediaCodec_queueInputBuffer(
+ codec.get(), bufidx,
+ 0 /* offset */, sampleSize, presentationTimeUs,
+ sawInputEOS ? AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM : 0);
+ if (mstatus != AMEDIA_OK) {
+ // AMEDIA_ERROR_UNKNOWN == { -ERANGE -EINVAL -EACCES }
+ ALOGE("%s: AMediaCodec_queueInputBuffer returned status %d,"
+ "short decode",
+ __func__, (int)mstatus);
+ break;
+ }
+ (void)AMediaExtractor_advance(ex.get());
+ }
+ }
+
+ AMediaCodecBufferInfo info;
+ const int status = AMediaCodec_dequeueOutputBuffer(codec.get(), &info, 1);
+ ALOGV("%s: dequeueoutput returned: %d", __func__, status);
+ if (status >= 0) {
+ if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
+ ALOGV("%s: output EOS", __func__);
+ sawOutputEOS = true;
+ }
+ ALOGV("%s: got decoded buffer size %d", __func__, info.size);
+
+ const uint8_t * const buf = AMediaCodec_getOutputBuffer(
+ codec.get(), status, nullptr /* out_size */);
+ if (buf == nullptr) {
+ ALOGE("%s: AMediaCodec_getOutputBuffer returned nullptr, short decode",
+ __func__);
+ break;
+ }
+ const size_t dataSize = std::min((size_t)info.size, available);
+ memcpy(writePos, buf + info.offset, dataSize);
+ writePos += dataSize;
+ written += dataSize;
+ available -= dataSize;
+ const media_status_t mstatus = AMediaCodec_releaseOutputBuffer(
+ codec.get(), status, false /* render */);
+ if (mstatus != AMEDIA_OK) {
+ // AMEDIA_ERROR_UNKNOWN == { -ERANGE -EINVAL -EACCES }
+ ALOGE("%s: AMediaCodec_releaseOutputBuffer"
+ " returned status %d, short decode",
+ __func__, (int)mstatus);
+ break;
+ }
+ if (available == 0) {
+ // there might be more data, but there's no space for it
+ sawOutputEOS = true;
+ }
+ } else if (status == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
+ ALOGV("%s: output buffers changed", __func__);
+ } else if (status == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
+ format.reset(AMediaCodec_getOutputFormat(codec.get())); // update format
+ ALOGV("%s: format changed to: %s",
+ __func__, AMediaFormat_toString(format.get()));
+ } else if (status == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
+ ALOGV("%s: no output buffer right now", __func__);
+ } else if (status <= AMEDIA_ERROR_BASE) {
+ ALOGE("%s: decode error: %d", __func__, status);
+ break;
+ } else {
+ ALOGV("%s: unexpected info code: %d", __func__, status);
+ }
+ }
+
+ (void)AMediaCodec_stop(codec.get());
+ if (!AMediaFormat_getInt32(
+ format.get(), AMEDIAFORMAT_KEY_SAMPLE_RATE, (int32_t*) rate) ||
+ !AMediaFormat_getInt32(
+ format.get(), AMEDIAFORMAT_KEY_CHANNEL_COUNT, channelCount)) {
+ return UNKNOWN_ERROR;
+ }
+ if (!AMediaFormat_getInt32(format.get(), AMEDIAFORMAT_KEY_CHANNEL_MASK,
+ (int32_t*) channelMask)) {
+ *channelMask = AUDIO_CHANNEL_NONE;
+ }
+ *sizeInBytes = written;
+ return OK;
+ }
+ }
+ return UNKNOWN_ERROR;
+}
+
+status_t Sound::doLoad()
+{
+ ALOGV("%s()", __func__);
+ status_t status = NO_INIT;
+ if (mFd.get() != -1) {
+ mHeap = new MemoryHeapBase(kDefaultHeapSize);
+
+ ALOGV("%s: start decode", __func__);
+ uint32_t sampleRate;
+ int32_t channelCount;
+ audio_format_t format;
+ audio_channel_mask_t channelMask;
+ status_t status = decode(mFd.get(), mOffset, mLength, &sampleRate, &channelCount, &format,
+ &channelMask, mHeap, &mSizeInBytes);
+ ALOGV("%s: close(%d)", __func__, mFd.get());
+ mFd.reset(); // close
+
+ if (status != NO_ERROR) {
+ ALOGE("%s: unable to load sound", __func__);
+ } else if (sampleRate > kMaxSampleRate) {
+ ALOGE("%s: sample rate (%u) out of range", __func__, sampleRate);
+ status = BAD_VALUE;
+ } else if (channelCount < 1 || channelCount > FCC_8) {
+ ALOGE("%s: sample channel count (%d) out of range", __func__, channelCount);
+ status = BAD_VALUE;
+ } else {
+ // Correctly loaded, proper parameters
+ ALOGV("%s: pointer = %p, sizeInBytes = %zu, sampleRate = %u, channelCount = %d",
+ __func__, mHeap->getBase(), mSizeInBytes, sampleRate, channelCount);
+ mData = new MemoryBase(mHeap, 0, mSizeInBytes);
+ mSampleRate = sampleRate;
+ mChannelCount = channelCount;
+ mFormat = format;
+ mChannelMask = channelMask;
+ mState = READY; // this should be last, as it is an atomic sync point
+ return NO_ERROR;
+ }
+ } else {
+ ALOGE("%s: uninitialized fd, dup failed", __func__);
+ }
+ // ERROR handling
+ mHeap.clear();
+ mState = DECODE_ERROR; // this should be last, as it is an atomic sync point
+ return status;
+}
+
+} // namespace android::soundpool
diff --git a/media/jni/soundpool/Sound.h b/media/jni/soundpool/Sound.h
new file mode 100644
index 0000000..efe940a
--- /dev/null
+++ b/media/jni/soundpool/Sound.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#pragma once
+
+#include <android-base/unique_fd.h>
+#include <binder/MemoryBase.h>
+#include <binder/MemoryHeapBase.h>
+#include <system/audio.h>
+
+namespace android::soundpool {
+
+class SoundDecoder;
+
+/**
+ * Sound is a resource used by SoundPool, referenced by soundID.
+ *
+ * After loading, it is effectively const so no locking required.
+ * However, in order to guarantee that all the values have been
+ * written properly and read properly, we use the mState as an atomic synchronization
+ * point. So if getState() shows READY, then all the other getters may
+ * be safely read.
+ *
+ * Technical details:
+ * We access the mState atomic value through memory_order_seq_cst
+ *
+ * https://en.cppreference.com/w/cpp/atomic/memory_order
+ *
+ * which provides memory barriers. So if the last value written by the SoundDecoder
+ * is mState, then the compiler ensures no other prior writes by SoundDecoder will be
+ * reordered afterwards, and memory barrier is placed (as necessary) to ensure the
+ * cache is visible to other processors.
+ *
+ * Likewise, if the first value read by SoundPool is mState,
+ * the compiler ensures no reads for that thread will be reordered before mState is read,
+ * and a memory barrier is placed (as necessary) to ensure that the cache is properly
+ * updated with other processor's writes before reading.
+ *
+ * See https://developer.android.com/training/articles/smp for discussions about
+ * the variant load-acquire, store-release semantics.
+ */
+class Sound {
+ friend SoundDecoder; // calls doLoad().
+
+public:
+ enum sound_state : int32_t { LOADING, READY, DECODE_ERROR };
+ // A sound starts in the LOADING state and transitions only once
+ // to either READY or DECODE_ERROR when doLoad() is called.
+
+ Sound(int soundID, int fd, int64_t offset, int64_t length);
+ ~Sound();
+
+ int32_t getSoundID() const { return mSoundID; }
+ int32_t getChannelCount() const { return mChannelCount; }
+ uint32_t getSampleRate() const { return mSampleRate; }
+ audio_format_t getFormat() const { return mFormat; }
+ audio_channel_mask_t getChannelMask() const { return mChannelMask; }
+ size_t getSizeInBytes() const { return mSizeInBytes; }
+ sound_state getState() const { return mState; }
+ uint8_t* getData() const { return static_cast<uint8_t*>(mData->unsecurePointer()); }
+ sp<IMemory> getIMemory() const { return mData; }
+
+private:
+ status_t doLoad(); // only SoundDecoder accesses this.
+
+ size_t mSizeInBytes = 0;
+ const int32_t mSoundID;
+ uint32_t mSampleRate = 0;
+ std::atomic<sound_state> mState = LOADING; // used as synchronization point
+ int32_t mChannelCount = 0;
+ audio_format_t mFormat = AUDIO_FORMAT_INVALID;
+ audio_channel_mask_t mChannelMask = AUDIO_CHANNEL_NONE;
+ base::unique_fd mFd; // initialized in constructor, reset to -1 after loading
+ const int64_t mOffset; // int64_t to match java long, see off64_t
+ const int64_t mLength; // int64_t to match java long, see off64_t
+ sp<IMemory> mData;
+ sp<MemoryHeapBase> mHeap;
+};
+
+} // namespace android::soundpool
diff --git a/media/jni/soundpool/SoundDecoder.cpp b/media/jni/soundpool/SoundDecoder.cpp
new file mode 100644
index 0000000..12200ef
--- /dev/null
+++ b/media/jni/soundpool/SoundDecoder.cpp
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SoundPool::SoundDecoder"
+#include "utils/Log.h"
+
+#include "SoundDecoder.h"
+
+namespace android::soundpool {
+
+// Maximum Samples that can be background decoded before we block the caller.
+static constexpr size_t kMaxQueueSize = 128;
+
+// The amount of time we wait for a new Sound decode request
+// before the SoundDecoder thread closes.
+static constexpr int32_t kWaitTimeBeforeCloseMs = 1000;
+
+SoundDecoder::SoundDecoder(SoundManager* soundManager, size_t threads)
+ : mSoundManager(soundManager)
+{
+ ALOGV("%s(%p, %zu)", __func__, soundManager, threads);
+ // ThreadPool is created, but we don't launch any threads.
+ mThreadPool = std::make_unique<ThreadPool>(
+ std::min(threads, (size_t)std::thread::hardware_concurrency()),
+ "SoundDecoder_");
+}
+
+SoundDecoder::~SoundDecoder()
+{
+ ALOGV("%s()", __func__);
+ quit();
+}
+
+void SoundDecoder::quit()
+{
+ ALOGV("%s()", __func__);
+ {
+ std::lock_guard lock(mLock);
+ mQuit = true;
+ mQueueSpaceAvailable.notify_all(); // notify all load waiters
+ mQueueDataAvailable.notify_all(); // notify all worker threads
+ }
+ mThreadPool->quit();
+}
+
+void SoundDecoder::run(int32_t id __unused /* ALOGV only */)
+{
+ ALOGV("%s(%d): entering", __func__, id);
+ std::unique_lock lock(mLock);
+ while (!mQuit) {
+ if (mSoundIDs.size() == 0) {
+ ALOGV("%s(%d): waiting", __func__, id);
+ mQueueDataAvailable.wait_for(
+ lock, std::chrono::duration<int32_t, std::milli>(kWaitTimeBeforeCloseMs));
+ if (mSoundIDs.size() == 0) {
+ break; // no new sound, exit this thread.
+ }
+ continue;
+ }
+ const int32_t soundID = mSoundIDs.front();
+ mSoundIDs.pop_front();
+ mQueueSpaceAvailable.notify_one();
+ ALOGV("%s(%d): processing soundID: %d size: %zu", __func__, id, soundID, mSoundIDs.size());
+ lock.unlock();
+ std::shared_ptr<Sound> sound = mSoundManager->findSound(soundID);
+ status_t status = NO_INIT;
+ if (sound.get() != nullptr) {
+ status = sound->doLoad();
+ }
+ ALOGV("%s(%d): notifying loaded soundID:%d status:%d", __func__, id, soundID, status);
+ mSoundManager->notify(SoundPoolEvent(SoundPoolEvent::SOUND_LOADED, soundID, status));
+ lock.lock();
+ }
+ ALOGV("%s(%d): exiting", __func__, id);
+}
+
+void SoundDecoder::loadSound(int32_t soundID)
+{
+ ALOGV("%s(%d)", __func__, soundID);
+ size_t pendingSounds;
+ {
+ std::unique_lock lock(mLock);
+ while (mSoundIDs.size() == kMaxQueueSize) {
+ if (mQuit) return;
+ ALOGV("%s: waiting soundID: %d size: %zu", __func__, soundID, mSoundIDs.size());
+ mQueueSpaceAvailable.wait(lock);
+ }
+ if (mQuit) return;
+ mSoundIDs.push_back(soundID);
+ mQueueDataAvailable.notify_one();
+ ALOGV("%s: adding soundID: %d size: %zu", __func__, soundID, mSoundIDs.size());
+ pendingSounds = mSoundIDs.size();
+ }
+ // Launch threads as needed. The "as needed" is weakly consistent as we release mLock.
+ if (pendingSounds > mThreadPool->getActiveThreadCount()) {
+ const int32_t id __unused = mThreadPool->launch([this](int32_t id) { run(id); });
+ ALOGV_IF(id != 0, "%s: launched thread %d", __func__, id);
+ }
+}
+
+} // end namespace android::soundpool
diff --git a/media/jni/soundpool/SoundDecoder.h b/media/jni/soundpool/SoundDecoder.h
new file mode 100644
index 0000000..1288943
--- /dev/null
+++ b/media/jni/soundpool/SoundDecoder.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#pragma once
+
+#include "SoundPool.h"
+
+#include <deque>
+#include <mutex>
+
+namespace android::soundpool {
+
+/**
+ * SoundDecoder handles background decoding tasks.
+ */
+class SoundDecoder {
+public:
+ SoundDecoder(SoundManager* soundManager, size_t threads);
+ ~SoundDecoder();
+ void loadSound(int32_t soundID);
+ void quit();
+
+private:
+ void run(int32_t id); // The decode thread function.
+
+ SoundManager* const mSoundManager; // set in constructor, has own lock
+ std::unique_ptr<ThreadPool> mThreadPool; // set in constructor, has own lock
+
+ std::mutex mLock;
+ std::condition_variable mQueueSpaceAvailable;
+ std::condition_variable mQueueDataAvailable;
+
+ std::deque<int32_t> mSoundIDs; // GUARDED_BY(mLock);
+ bool mQuit = false; // GUARDED_BY(mLock);
+};
+
+} // end namespace android::soundpool
+
diff --git a/media/jni/soundpool/SoundManager.cpp b/media/jni/soundpool/SoundManager.cpp
new file mode 100644
index 0000000..3c625bf
--- /dev/null
+++ b/media/jni/soundpool/SoundManager.cpp
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SoundPool::SoundManager"
+#include <utils/Log.h>
+
+#include "SoundManager.h"
+
+#include <thread>
+
+#include "SoundDecoder.h"
+
+namespace android::soundpool {
+
+static const size_t kDecoderThreads = std::thread::hardware_concurrency() >= 4 ? 2 : 1;
+
+SoundManager::SoundManager()
+ : mDecoder{std::make_unique<SoundDecoder>(this, kDecoderThreads)}
+{
+ ALOGV("%s()", __func__);
+}
+
+SoundManager::~SoundManager()
+{
+ ALOGV("%s()", __func__);
+ mDecoder->quit();
+
+ std::lock_guard lock(mSoundManagerLock);
+ mSounds.clear();
+}
+
+int32_t SoundManager::load(int fd, int64_t offset, int64_t length, int32_t priority __unused)
+{
+ ALOGV("%s(fd=%d, offset=%lld, length=%lld, priority=%d)",
+ __func__, fd, (long long)offset, (long long)length, priority);
+ int32_t soundID;
+ {
+ std::lock_guard lock(mSoundManagerLock);
+ // mNextSoundID is always positive and does not "integer overflow"
+ do {
+ mNextSoundID = mNextSoundID == INT32_MAX ? 1 : mNextSoundID + 1;
+ } while (findSound_l(mNextSoundID) != nullptr);
+ soundID = mNextSoundID;
+ auto sound = std::make_shared<Sound>(soundID, fd, offset, length);
+ mSounds.emplace(soundID, sound);
+ }
+ // mDecoder->loadSound() must be called outside of mSoundManagerLock.
+ // mDecoder->loadSound() may block on mDecoder message queue space;
+ // the message queue emptying may block on SoundManager::findSound().
+ //
+ // It is theoretically possible that sound loads might decode out-of-order.
+ mDecoder->loadSound(soundID);
+ return soundID;
+}
+
+bool SoundManager::unload(int32_t soundID)
+{
+ ALOGV("%s(soundID=%d)", __func__, soundID);
+ std::lock_guard lock(mSoundManagerLock);
+ return mSounds.erase(soundID) > 0; // erase() returns number of sounds removed.
+}
+
+std::shared_ptr<Sound> SoundManager::findSound(int32_t soundID) const
+{
+ std::lock_guard lock(mSoundManagerLock);
+ return findSound_l(soundID);
+}
+
+std::shared_ptr<Sound> SoundManager::findSound_l(int32_t soundID) const
+{
+ auto it = mSounds.find(soundID);
+ return it != mSounds.end() ? it->second : nullptr;
+}
+
+void SoundManager::setCallback(SoundPool *soundPool, SoundPoolCallback* callback, void* user)
+{
+ mCallbackHandler.setCallback(soundPool, callback, user);
+}
+
+void SoundManager::notify(SoundPoolEvent event)
+{
+ mCallbackHandler.notify(event);
+}
+
+void* SoundManager::getUserData() const
+{
+ return mCallbackHandler.getUserData();
+}
+
+} // namespace android::soundpool
diff --git a/media/jni/soundpool/SoundManager.h b/media/jni/soundpool/SoundManager.h
new file mode 100644
index 0000000..9201e78
--- /dev/null
+++ b/media/jni/soundpool/SoundManager.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#pragma once
+
+#include "Sound.h"
+
+#include <mutex>
+#include <unordered_map>
+
+namespace android {
+
+class SoundPool;
+
+// for queued events
+class SoundPoolEvent {
+public:
+ explicit SoundPoolEvent(int msg, int arg1 = 0, int arg2 = 0) :
+ mMsg(msg), mArg1(arg1), mArg2(arg2) {}
+ const int mMsg; // MessageType
+ const int mArg1; // soundID
+ const int mArg2; // status
+ enum MessageType { INVALID, SOUND_LOADED };
+};
+
+// callback function prototype
+typedef void SoundPoolCallback(SoundPoolEvent event, SoundPool* soundPool, void* user);
+
+} // namespace android
+
+namespace android::soundpool {
+
+// This class manages Sounds for the SoundPool.
+class SoundManager {
+public:
+ SoundManager();
+ ~SoundManager();
+
+ // Matches corresponding SoundPool API functions
+ int32_t load(int fd, int64_t offset, int64_t length, int32_t priority);
+ bool unload(int32_t soundID);
+ void setCallback(SoundPool* soundPool, SoundPoolCallback* callback, void* user);
+ void* getUserData() const;
+
+ // SoundPool and SoundDecoder access
+ std::shared_ptr<Sound> findSound(int32_t soundID) const;
+
+ // from the SoundDecoder
+ void notify(SoundPoolEvent event);
+
+private:
+
+ // CallbackHandler is used to manage notifications back to the app when a sound
+ // has been loaded. It uses a recursive lock to allow setting the callback
+ // during the callback.
+ class CallbackHandler {
+ public:
+ void setCallback(SoundPool *soundPool, SoundPoolCallback* callback, void* userData)
+ {
+ std::lock_guard<std::recursive_mutex> lock(mCallbackLock);
+ mSoundPool = soundPool;
+ mCallback = callback;
+ mUserData = userData;
+ }
+ void notify(SoundPoolEvent event) const
+ {
+ std::lock_guard<std::recursive_mutex> lock(mCallbackLock);
+ if (mCallback != nullptr) {
+ mCallback(event, mSoundPool, mUserData);
+ // Note: mCallback may call setCallback().
+ // so mCallback, mUserData may have changed.
+ }
+ }
+ void* getUserData() const
+ {
+ std::lock_guard<std::recursive_mutex> lock(mCallbackLock);
+ return mUserData;
+ }
+ private:
+ mutable std::recursive_mutex mCallbackLock; // allow mCallback to setCallback().
+ SoundPool* mSoundPool = nullptr; // GUARDED_BY(mCallbackLock)
+ SoundPoolCallback* mCallback = nullptr; // GUARDED_BY(mCallbackLock)
+ void* mUserData = nullptr; // GUARDED_BY(mCallbackLock)
+ };
+
+ std::shared_ptr<Sound> findSound_l(int32_t soundID) const;
+
+ // The following variables are initialized in constructor and can be accessed anytime.
+ CallbackHandler mCallbackHandler; // has its own lock
+ const std::unique_ptr<SoundDecoder> mDecoder; // has its own lock
+
+ mutable std::mutex mSoundManagerLock;
+ std::unordered_map<int, std::shared_ptr<Sound>> mSounds; // GUARDED_BY(mSoundManagerLock)
+ int32_t mNextSoundID = 0; // GUARDED_BY(mSoundManagerLock)
+};
+
+} // namespace android::soundpool
diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp
index 102bbf0..ac44843 100644
--- a/media/jni/soundpool/SoundPool.cpp
+++ b/media/jni/soundpool/SoundPool.cpp
@@ -16,1124 +16,230 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "SoundPool"
-
-#include <chrono>
-#include <inttypes.h>
-#include <thread>
#include <utils/Log.h>
-#define USE_SHARED_MEM_BUFFER
+#include <algorithm>
+#include <thread>
-#include <media/AudioTrack.h>
#include "SoundPool.h"
-#include "SoundPoolThread.h"
-#include <media/NdkMediaCodec.h>
-#include <media/NdkMediaExtractor.h>
-#include <media/NdkMediaFormat.h>
namespace android
{
-int kDefaultBufferCount = 4;
-uint32_t kMaxSampleRate = 48000;
-uint32_t kDefaultSampleRate = 44100;
-uint32_t kDefaultFrameCount = 1200;
-size_t kDefaultHeapSize = 1024 * 1024; // 1MB
+// kManagerThreads = 1 historically.
+// Not really necessary to have more than one, but it does speed things up by about
+// 25% having 2 threads instead of 1 when playing many sounds. Having many threads
+// could starve other AudioFlinger clients with SoundPool activity. It may also cause
+// issues with app loading, e.g. Camera.
+static const size_t kStreamManagerThreads = std::thread::hardware_concurrency() >= 4 ? 2 : 1;
+// kUseApiLock = true prior to R.
+// Set to true to prevent multiple users access internal to the SoundPool API.
+// Set to false to make the SoundPool methods weakly consistent. When set to false,
+// only AutoPause and AutoResume are locked, which are the only two methods that
+// require API level locking for consistency.
+static constexpr bool kUseApiLock = false;
-SoundPool::SoundPool(int maxChannels, const audio_attributes_t* pAttributes)
+namespace {
+// Check input arguments to SoundPool - return "true" to reject request.
+
+bool checkVolume(float *leftVolume, float *rightVolume)
{
- ALOGV("SoundPool constructor: maxChannels=%d, attr.usage=%d, attr.flags=0x%x, attr.tags=%s",
- maxChannels, pAttributes->usage, pAttributes->flags, pAttributes->tags);
-
- // check limits
- mMaxChannels = maxChannels;
- if (mMaxChannels < 1) {
- mMaxChannels = 1;
+ if (*leftVolume != std::clamp(*leftVolume, 0.f, 1.f) ||
+ *rightVolume != std::clamp(*rightVolume, 0.f, 1.f)) {
+ ALOGI("volume l=%f r=%f out of (0.f, 1.f) bounds, using 1.f", *leftVolume, *rightVolume);
+ // for backward compatibility use 1.f.
+ *leftVolume = *rightVolume = 1.f;
}
- else if (mMaxChannels > 32) {
- mMaxChannels = 32;
+ return false;
+}
+
+bool checkRate(float *rate)
+{
+ if (*rate != std::clamp(*rate, 0.125f, 8.f)) {
+ ALOGI("rate %f out of (0.125f, 8.f) bounds, clamping", *rate);
+ // for backward compatibility just clamp
+ *rate = std::clamp(*rate, 0.125f, 8.f);
}
- ALOGW_IF(maxChannels != mMaxChannels, "App requested %d channels", maxChannels);
+ return false;
+}
- mQuit = false;
- mMuted = false;
- mDecodeThread = 0;
- memcpy(&mAttributes, pAttributes, sizeof(audio_attributes_t));
- mAllocated = 0;
- mNextSampleID = 0;
- mNextChannelID = 0;
-
- mCallback = 0;
- mUserData = 0;
-
- mChannelPool = new SoundChannel[mMaxChannels];
- for (int i = 0; i < mMaxChannels; ++i) {
- mChannelPool[i].init(this);
- mChannels.push_back(&mChannelPool[i]);
+bool checkPriority(int32_t *priority)
+{
+ if (*priority < 0) {
+ ALOGI("negative priority %d, should be >= 0.", *priority);
+ // for backward compatibility, ignore.
}
+ return false;
+}
- // start decode thread
- startThreads();
+bool checkLoop(int32_t *loop)
+{
+ if (*loop < -1) {
+ ALOGI("loop %d, should be >= -1", *loop);
+ *loop = -1;
+ }
+ return false;
+}
+
+} // namespace
+
+SoundPool::SoundPool(int32_t maxStreams, const audio_attributes_t* attributes)
+ : mStreamManager(maxStreams, kStreamManagerThreads, attributes)
+{
+ ALOGV("%s(maxStreams=%d, attr={ content_type=%d, usage=%d, flags=0x%x, tags=%s })",
+ __func__, maxStreams,
+ attributes->content_type, attributes->usage, attributes->flags, attributes->tags);
}
SoundPool::~SoundPool()
{
- ALOGV("SoundPool destructor");
- mDecodeThread->quit();
- quit();
-
- Mutex::Autolock lock(&mLock);
-
- mChannels.clear();
- if (mChannelPool)
- delete [] mChannelPool;
- // clean up samples
- ALOGV("clear samples");
- mSamples.clear();
-
- if (mDecodeThread)
- delete mDecodeThread;
+ ALOGV("%s()", __func__);
}
-void SoundPool::addToRestartList(SoundChannel* channel)
+int32_t SoundPool::load(int fd, int64_t offset, int64_t length, int32_t priority)
{
- Mutex::Autolock lock(&mRestartLock);
- if (!mQuit) {
- mRestart.push_back(channel);
- mCondition.signal();
- }
+ ALOGV("%s(fd=%d, offset=%lld, length=%lld, priority=%d)",
+ __func__, fd, (long long)offset, (long long)length, priority);
+ auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+ return mSoundManager.load(fd, offset, length, priority);
}
-void SoundPool::addToStopList(SoundChannel* channel)
+bool SoundPool::unload(int32_t soundID)
{
- Mutex::Autolock lock(&mRestartLock);
- if (!mQuit) {
- mStop.push_back(channel);
- mCondition.signal();
- }
+ ALOGV("%s(%d)", __func__, soundID);
+ auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+ return mSoundManager.unload(soundID);
}
-int SoundPool::beginThread(void* arg)
+int32_t SoundPool::play(int32_t soundID, float leftVolume, float rightVolume,
+ int32_t priority, int32_t loop, float rate)
{
- SoundPool* p = (SoundPool*)arg;
- return p->run();
-}
+ ALOGV("%s(soundID=%d, leftVolume=%f, rightVolume=%f, priority=%d, loop=%d, rate=%f)",
+ __func__, soundID, leftVolume, rightVolume, priority, loop, rate);
-int SoundPool::run()
-{
- mRestartLock.lock();
- while (!mQuit) {
- mCondition.wait(mRestartLock);
- ALOGV("awake");
- if (mQuit) break;
+ // New for R: check arguments to ensure track can be created.
+ // If SoundPool defers the creation of the AudioTrack to the StreamManager thread,
+ // the failure to create may not be visible to the caller, so this precheck is needed.
+ if (checkVolume(&leftVolume, &rightVolume)
+ || checkPriority(&priority)
+ || checkLoop(&loop)
+ || checkRate(&rate)) return 0;
- while (!mStop.empty()) {
- SoundChannel* channel;
- ALOGV("Getting channel from stop list");
- List<SoundChannel* >::iterator iter = mStop.begin();
- channel = *iter;
- mStop.erase(iter);
- mRestartLock.unlock();
- if (channel != 0) {
- Mutex::Autolock lock(&mLock);
- channel->stop();
- }
- mRestartLock.lock();
- if (mQuit) break;
- }
-
- while (!mRestart.empty()) {
- SoundChannel* channel;
- ALOGV("Getting channel from list");
- List<SoundChannel*>::iterator iter = mRestart.begin();
- channel = *iter;
- mRestart.erase(iter);
- mRestartLock.unlock();
- if (channel != 0) {
- Mutex::Autolock lock(&mLock);
- channel->nextEvent();
- }
- mRestartLock.lock();
- if (mQuit) break;
- }
- }
-
- mStop.clear();
- mRestart.clear();
- mCondition.signal();
- mRestartLock.unlock();
- ALOGV("goodbye");
- return 0;
-}
-
-void SoundPool::quit()
-{
- mRestartLock.lock();
- mQuit = true;
- mCondition.signal();
- mCondition.wait(mRestartLock);
- ALOGV("return from quit");
- mRestartLock.unlock();
-}
-
-bool SoundPool::startThreads()
-{
- createThreadEtc(beginThread, this, "SoundPool");
- if (mDecodeThread == NULL)
- mDecodeThread = new SoundPoolThread(this);
- return mDecodeThread != NULL;
-}
-
-sp<Sample> SoundPool::findSample(int sampleID)
-{
- Mutex::Autolock lock(&mLock);
- return findSample_l(sampleID);
-}
-
-sp<Sample> SoundPool::findSample_l(int sampleID)
-{
- return mSamples.valueFor(sampleID);
-}
-
-SoundChannel* SoundPool::findChannel(int channelID)
-{
- for (int i = 0; i < mMaxChannels; ++i) {
- if (mChannelPool[i].channelID() == channelID) {
- return &mChannelPool[i];
- }
- }
- return NULL;
-}
-
-SoundChannel* SoundPool::findNextChannel(int channelID)
-{
- for (int i = 0; i < mMaxChannels; ++i) {
- if (mChannelPool[i].nextChannelID() == channelID) {
- return &mChannelPool[i];
- }
- }
- return NULL;
-}
-
-int SoundPool::load(int fd, int64_t offset, int64_t length, int priority __unused)
-{
- ALOGV("load: fd=%d, offset=%" PRId64 ", length=%" PRId64 ", priority=%d",
- fd, offset, length, priority);
- int sampleID;
- {
- Mutex::Autolock lock(&mLock);
- sampleID = ++mNextSampleID;
- sp<Sample> sample = new Sample(sampleID, fd, offset, length);
- mSamples.add(sampleID, sample);
- sample->startLoad();
- }
- // mDecodeThread->loadSample() must be called outside of mLock.
- // mDecodeThread->loadSample() may block on mDecodeThread message queue space;
- // the message queue emptying may block on SoundPool::findSample().
- //
- // It theoretically possible that sample loads might decode out-of-order.
- mDecodeThread->loadSample(sampleID);
- return sampleID;
-}
-
-bool SoundPool::unload(int sampleID)
-{
- ALOGV("unload: sampleID=%d", sampleID);
- Mutex::Autolock lock(&mLock);
- return mSamples.removeItem(sampleID) >= 0; // removeItem() returns index or BAD_VALUE
-}
-
-int SoundPool::play(int sampleID, float leftVolume, float rightVolume,
- int priority, int loop, float rate)
-{
- ALOGV("play sampleID=%d, leftVolume=%f, rightVolume=%f, priority=%d, loop=%d, rate=%f",
- sampleID, leftVolume, rightVolume, priority, loop, rate);
- SoundChannel* channel;
- int channelID;
-
- Mutex::Autolock lock(&mLock);
-
- if (mQuit) {
- return 0;
- }
- // is sample ready?
- sp<Sample> sample(findSample_l(sampleID));
- if ((sample == 0) || (sample->state() != Sample::READY)) {
- ALOGW(" sample %d not READY", sampleID);
+ auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+ const std::shared_ptr<soundpool::Sound> sound = mSoundManager.findSound(soundID);
+ if (sound == nullptr || sound->getState() != soundpool::Sound::READY) {
+ ALOGW("%s soundID %d not READY", __func__, soundID);
return 0;
}
- dump();
-
- // allocate a channel
- channel = allocateChannel_l(priority, sampleID);
-
- // no channel allocated - return 0
- if (!channel) {
- ALOGV("No channel allocated");
- return 0;
- }
-
- channelID = ++mNextChannelID;
-
- ALOGV("play channel %p state = %d", channel, channel->state());
- channel->play(sample, channelID, leftVolume, rightVolume, priority, loop, rate);
- return channelID;
-}
-
-SoundChannel* SoundPool::allocateChannel_l(int priority, int sampleID)
-{
- List<SoundChannel*>::iterator iter;
- SoundChannel* channel = NULL;
-
- // check if channel for given sampleID still available
- if (!mChannels.empty()) {
- for (iter = mChannels.begin(); iter != mChannels.end(); ++iter) {
- if (sampleID == (*iter)->getPrevSampleID() && (*iter)->state() == SoundChannel::IDLE) {
- channel = *iter;
- mChannels.erase(iter);
- ALOGV("Allocated recycled channel for same sampleID");
- break;
- }
- }
- }
-
- // allocate any channel
- if (!channel && !mChannels.empty()) {
- iter = mChannels.begin();
- if (priority >= (*iter)->priority()) {
- channel = *iter;
- mChannels.erase(iter);
- ALOGV("Allocated active channel");
- }
- }
-
- // update priority and put it back in the list
- if (channel) {
- channel->setPriority(priority);
- for (iter = mChannels.begin(); iter != mChannels.end(); ++iter) {
- if (priority < (*iter)->priority()) {
- break;
- }
- }
- mChannels.insert(iter, channel);
- }
- return channel;
-}
-
-// move a channel from its current position to the front of the list
-void SoundPool::moveToFront_l(SoundChannel* channel)
-{
- for (List<SoundChannel*>::iterator iter = mChannels.begin(); iter != mChannels.end(); ++iter) {
- if (*iter == channel) {
- mChannels.erase(iter);
- mChannels.push_front(channel);
- break;
- }
- }
-}
-
-void SoundPool::pause(int channelID)
-{
- ALOGV("pause(%d)", channelID);
- Mutex::Autolock lock(&mLock);
- SoundChannel* channel = findChannel(channelID);
- if (channel) {
- channel->pause();
- }
+ const int32_t streamID = mStreamManager.queueForPlay(
+ sound, soundID, leftVolume, rightVolume, priority, loop, rate);
+ ALOGV("%s returned %d", __func__, streamID);
+ return streamID;
}
void SoundPool::autoPause()
{
- ALOGV("autoPause()");
- Mutex::Autolock lock(&mLock);
- for (int i = 0; i < mMaxChannels; ++i) {
- SoundChannel* channel = &mChannelPool[i];
- channel->autoPause();
- }
-}
-
-void SoundPool::resume(int channelID)
-{
- ALOGV("resume(%d)", channelID);
- Mutex::Autolock lock(&mLock);
- SoundChannel* channel = findChannel(channelID);
- if (channel) {
- channel->resume();
- }
-}
-
-void SoundPool::mute(bool muting)
-{
- ALOGV("mute(%d)", muting);
- Mutex::Autolock lock(&mLock);
- mMuted = muting;
- if (!mChannels.empty()) {
- for (List<SoundChannel*>::iterator iter = mChannels.begin();
- iter != mChannels.end(); ++iter) {
- (*iter)->mute(muting);
- }
- }
+ ALOGV("%s()", __func__);
+ auto apiLock = std::make_unique<std::lock_guard<std::mutex>>(mApiLock);
+ mStreamManager.forEach([](soundpool::Stream *stream) { stream->autoPause(); });
}
void SoundPool::autoResume()
{
- ALOGV("autoResume()");
- Mutex::Autolock lock(&mLock);
- for (int i = 0; i < mMaxChannels; ++i) {
- SoundChannel* channel = &mChannelPool[i];
- channel->autoResume();
+ ALOGV("%s()", __func__);
+ auto apiLock = std::make_unique<std::lock_guard<std::mutex>>(mApiLock);
+ mStreamManager.forEach([](soundpool::Stream *stream) { stream->autoResume(); });
+}
+
+void SoundPool::mute(bool muting)
+{
+ ALOGV("%s(%d)", __func__, muting);
+ auto apiLock = std::make_unique<std::lock_guard<std::mutex>>(mApiLock);
+ mStreamManager.forEach([=](soundpool::Stream *stream) { stream->mute(muting); });
+}
+
+void SoundPool::pause(int32_t streamID)
+{
+ ALOGV("%s(%d)", __func__, streamID);
+ auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+ if (soundpool::Stream* stream = mStreamManager.findStream(streamID)) {
+ stream->pause(streamID);
}
}
-void SoundPool::stop(int channelID)
+void SoundPool::resume(int32_t streamID)
{
- ALOGV("stop(%d)", channelID);
- Mutex::Autolock lock(&mLock);
- SoundChannel* channel = findChannel(channelID);
- if (channel) {
- channel->stop();
- } else {
- channel = findNextChannel(channelID);
- if (channel)
- channel->clearNextEvent();
+ ALOGV("%s(%d)", __func__, streamID);
+ auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+ if (soundpool::Stream* stream = mStreamManager.findStream(streamID)) {
+ stream->resume(streamID);
}
}
-void SoundPool::setVolume(int channelID, float leftVolume, float rightVolume)
+void SoundPool::stop(int32_t streamID)
{
- Mutex::Autolock lock(&mLock);
- SoundChannel* channel = findChannel(channelID);
- if (channel) {
- channel->setVolume(leftVolume, rightVolume);
+ ALOGV("%s(%d)", __func__, streamID);
+ auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+ soundpool::Stream* stream = mStreamManager.findStream(streamID);
+ if (stream != nullptr && stream->requestStop(streamID)) {
+ mStreamManager.moveToRestartQueue(stream);
}
}
-void SoundPool::setPriority(int channelID, int priority)
+void SoundPool::setVolume(int32_t streamID, float leftVolume, float rightVolume)
{
- ALOGV("setPriority(%d, %d)", channelID, priority);
- Mutex::Autolock lock(&mLock);
- SoundChannel* channel = findChannel(channelID);
- if (channel) {
- channel->setPriority(priority);
+ ALOGV("%s(%d, %f %f)", __func__, streamID, leftVolume, rightVolume);
+ if (checkVolume(&leftVolume, &rightVolume)) return;
+ auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+ if (soundpool::Stream* stream = mStreamManager.findStream(streamID)) {
+ stream->setVolume(streamID, leftVolume, rightVolume);
}
}
-void SoundPool::setLoop(int channelID, int loop)
+void SoundPool::setPriority(int32_t streamID, int32_t priority)
{
- ALOGV("setLoop(%d, %d)", channelID, loop);
- Mutex::Autolock lock(&mLock);
- SoundChannel* channel = findChannel(channelID);
- if (channel) {
- channel->setLoop(loop);
+ ALOGV("%s(%d, %d)", __func__, streamID, priority);
+ if (checkPriority(&priority)) return;
+ auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+ if (soundpool::Stream* stream = mStreamManager.findStream(streamID)) {
+ stream->setPriority(streamID, priority);
}
}
-void SoundPool::setRate(int channelID, float rate)
+void SoundPool::setLoop(int32_t streamID, int32_t loop)
{
- ALOGV("setRate(%d, %f)", channelID, rate);
- Mutex::Autolock lock(&mLock);
- SoundChannel* channel = findChannel(channelID);
- if (channel) {
- channel->setRate(rate);
+ ALOGV("%s(%d, %d)", __func__, streamID, loop);
+ if (checkLoop(&loop)) return;
+ auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+ if (soundpool::Stream* stream = mStreamManager.findStream(streamID)) {
+ stream->setLoop(streamID, loop);
}
}
-// call with lock held
-void SoundPool::done_l(SoundChannel* channel)
+void SoundPool::setRate(int32_t streamID, float rate)
{
- ALOGV("done_l(%d)", channel->channelID());
- // if "stolen", play next event
- if (channel->nextChannelID() != 0) {
- ALOGV("add to restart list");
- addToRestartList(channel);
- }
-
- // return to idle state
- else {
- ALOGV("move to front");
- moveToFront_l(channel);
+ ALOGV("%s(%d, %f)", __func__, streamID, rate);
+ if (checkRate(&rate)) return;
+ auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+ if (soundpool::Stream* stream = mStreamManager.findStream(streamID)) {
+ stream->setRate(streamID, rate);
}
}
void SoundPool::setCallback(SoundPoolCallback* callback, void* user)
{
- Mutex::Autolock lock(&mCallbackLock);
- mCallback = callback;
- mUserData = user;
+ ALOGV("%s(%p, %p)", __func__, callback, user);
+ auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+ mSoundManager.setCallback(this, callback, user);
}
-void SoundPool::notify(SoundPoolEvent event)
+void* SoundPool::getUserData() const
{
- Mutex::Autolock lock(&mCallbackLock);
- if (mCallback != NULL) {
- mCallback(event, this, mUserData);
- }
-}
-
-void SoundPool::dump()
-{
- for (int i = 0; i < mMaxChannels; ++i) {
- mChannelPool[i].dump();
- }
-}
-
-
-Sample::Sample(int sampleID, int fd, int64_t offset, int64_t length)
-{
- init();
- mSampleID = sampleID;
- mFd = dup(fd);
- mOffset = offset;
- mLength = length;
- ALOGV("create sampleID=%d, fd=%d, offset=%" PRId64 " length=%" PRId64,
- mSampleID, mFd, mLength, mOffset);
-}
-
-void Sample::init()
-{
- mSize = 0;
- mRefCount = 0;
- mSampleID = 0;
- mState = UNLOADED;
- mFd = -1;
- mOffset = 0;
- mLength = 0;
-}
-
-Sample::~Sample()
-{
- ALOGV("Sample::destructor sampleID=%d, fd=%d", mSampleID, mFd);
- if (mFd > 0) {
- ALOGV("close(%d)", mFd);
- ::close(mFd);
- }
-}
-
-static status_t decode(int fd, int64_t offset, int64_t length,
- uint32_t *rate, int *numChannels, audio_format_t *audioFormat,
- audio_channel_mask_t *channelMask, sp<MemoryHeapBase> heap,
- size_t *memsize) {
-
- ALOGV("fd %d, offset %" PRId64 ", size %" PRId64, fd, offset, length);
- AMediaExtractor *ex = AMediaExtractor_new();
- status_t err = AMediaExtractor_setDataSourceFd(ex, fd, offset, length);
-
- if (err != AMEDIA_OK) {
- AMediaExtractor_delete(ex);
- return err;
- }
-
- *audioFormat = AUDIO_FORMAT_PCM_16_BIT;
-
- size_t numTracks = AMediaExtractor_getTrackCount(ex);
- for (size_t i = 0; i < numTracks; i++) {
- AMediaFormat *format = AMediaExtractor_getTrackFormat(ex, i);
- const char *mime;
- if (!AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime)) {
- AMediaExtractor_delete(ex);
- AMediaFormat_delete(format);
- return UNKNOWN_ERROR;
- }
- if (strncmp(mime, "audio/", 6) == 0) {
-
- AMediaCodec *codec = AMediaCodec_createDecoderByType(mime);
- if (codec == NULL
- || AMediaCodec_configure(codec, format,
- NULL /* window */, NULL /* drm */, 0 /* flags */) != AMEDIA_OK
- || AMediaCodec_start(codec) != AMEDIA_OK
- || AMediaExtractor_selectTrack(ex, i) != AMEDIA_OK) {
- AMediaExtractor_delete(ex);
- AMediaCodec_delete(codec);
- AMediaFormat_delete(format);
- return UNKNOWN_ERROR;
- }
-
- bool sawInputEOS = false;
- bool sawOutputEOS = false;
- uint8_t* writePos = static_cast<uint8_t*>(heap->getBase());
- size_t available = heap->getSize();
- size_t written = 0;
-
- AMediaFormat_delete(format);
- format = AMediaCodec_getOutputFormat(codec);
-
- while (!sawOutputEOS) {
- if (!sawInputEOS) {
- ssize_t bufidx = AMediaCodec_dequeueInputBuffer(codec, 5000);
- ALOGV("input buffer %zd", bufidx);
- if (bufidx >= 0) {
- size_t bufsize;
- uint8_t *buf = AMediaCodec_getInputBuffer(codec, bufidx, &bufsize);
- if (buf == nullptr) {
- ALOGE("AMediaCodec_getInputBuffer returned nullptr, short decode");
- break;
- }
- int sampleSize = AMediaExtractor_readSampleData(ex, buf, bufsize);
- ALOGV("read %d", sampleSize);
- if (sampleSize < 0) {
- sampleSize = 0;
- sawInputEOS = true;
- ALOGV("EOS");
- }
- int64_t presentationTimeUs = AMediaExtractor_getSampleTime(ex);
-
- media_status_t mstatus = AMediaCodec_queueInputBuffer(codec, bufidx,
- 0 /* offset */, sampleSize, presentationTimeUs,
- sawInputEOS ? AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM : 0);
- if (mstatus != AMEDIA_OK) {
- // AMEDIA_ERROR_UNKNOWN == { -ERANGE -EINVAL -EACCES }
- ALOGE("AMediaCodec_queueInputBuffer returned status %d, short decode",
- (int)mstatus);
- break;
- }
- (void)AMediaExtractor_advance(ex);
- }
- }
-
- AMediaCodecBufferInfo info;
- int status = AMediaCodec_dequeueOutputBuffer(codec, &info, 1);
- ALOGV("dequeueoutput returned: %d", status);
- if (status >= 0) {
- if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
- ALOGV("output EOS");
- sawOutputEOS = true;
- }
- ALOGV("got decoded buffer size %d", info.size);
-
- uint8_t *buf = AMediaCodec_getOutputBuffer(codec, status, NULL /* out_size */);
- if (buf == nullptr) {
- ALOGE("AMediaCodec_getOutputBuffer returned nullptr, short decode");
- break;
- }
- size_t dataSize = info.size;
- if (dataSize > available) {
- dataSize = available;
- }
- memcpy(writePos, buf + info.offset, dataSize);
- writePos += dataSize;
- written += dataSize;
- available -= dataSize;
- media_status_t mstatus = AMediaCodec_releaseOutputBuffer(
- codec, status, false /* render */);
- if (mstatus != AMEDIA_OK) {
- // AMEDIA_ERROR_UNKNOWN == { -ERANGE -EINVAL -EACCES }
- ALOGE("AMediaCodec_releaseOutputBuffer returned status %d, short decode",
- (int)mstatus);
- break;
- }
- if (available == 0) {
- // there might be more data, but there's no space for it
- sawOutputEOS = true;
- }
- } else if (status == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
- ALOGV("output buffers changed");
- } else if (status == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
- AMediaFormat_delete(format);
- format = AMediaCodec_getOutputFormat(codec);
- ALOGV("format changed to: %s", AMediaFormat_toString(format));
- } else if (status == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
- ALOGV("no output buffer right now");
- } else if (status <= AMEDIA_ERROR_BASE) {
- ALOGE("decode error: %d", status);
- break;
- } else {
- ALOGV("unexpected info code: %d", status);
- }
- }
-
- (void)AMediaCodec_stop(codec);
- (void)AMediaCodec_delete(codec);
- (void)AMediaExtractor_delete(ex);
- if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, (int32_t*) rate) ||
- !AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT, numChannels)) {
- (void)AMediaFormat_delete(format);
- return UNKNOWN_ERROR;
- }
- if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_CHANNEL_MASK,
- (int32_t*) channelMask)) {
- *channelMask = AUDIO_CHANNEL_NONE;
- }
- (void)AMediaFormat_delete(format);
- *memsize = written;
- return OK;
- }
- (void)AMediaFormat_delete(format);
- }
- (void)AMediaExtractor_delete(ex);
- return UNKNOWN_ERROR;
-}
-
-status_t Sample::doLoad()
-{
- uint32_t sampleRate;
- int numChannels;
- audio_format_t format;
- audio_channel_mask_t channelMask;
- status_t status;
- mHeap = new MemoryHeapBase(kDefaultHeapSize);
-
- ALOGV("Start decode");
- status = decode(mFd, mOffset, mLength, &sampleRate, &numChannels, &format,
- &channelMask, mHeap, &mSize);
- ALOGV("close(%d)", mFd);
- ::close(mFd);
- mFd = -1;
- if (status != NO_ERROR) {
- ALOGE("Unable to load sample");
- goto error;
- }
- ALOGV("pointer = %p, size = %zu, sampleRate = %u, numChannels = %d",
- mHeap->getBase(), mSize, sampleRate, numChannels);
-
- if (sampleRate > kMaxSampleRate) {
- ALOGE("Sample rate (%u) out of range", sampleRate);
- status = BAD_VALUE;
- goto error;
- }
-
- if ((numChannels < 1) || (numChannels > FCC_8)) {
- ALOGE("Sample channel count (%d) out of range", numChannels);
- status = BAD_VALUE;
- goto error;
- }
-
- mData = new MemoryBase(mHeap, 0, mSize);
- mSampleRate = sampleRate;
- mNumChannels = numChannels;
- mFormat = format;
- mChannelMask = channelMask;
- mState = READY;
- return NO_ERROR;
-
-error:
- mHeap.clear();
- return status;
-}
-
-
-void SoundChannel::init(SoundPool* soundPool)
-{
- mSoundPool = soundPool;
- mPrevSampleID = -1;
-}
-
-// call with sound pool lock held
-void SoundChannel::play(const sp<Sample>& sample, int nextChannelID, float leftVolume,
- float rightVolume, int priority, int loop, float rate)
-{
- sp<AudioTrack> oldTrack;
- sp<AudioTrack> newTrack;
- status_t status = NO_ERROR;
-
- { // scope for the lock
- Mutex::Autolock lock(&mLock);
-
- ALOGV("SoundChannel::play %p: sampleID=%d, channelID=%d, leftVolume=%f, rightVolume=%f,"
- " priority=%d, loop=%d, rate=%f",
- this, sample->sampleID(), nextChannelID, leftVolume, rightVolume,
- priority, loop, rate);
-
- // if not idle, this voice is being stolen
- if (mState != IDLE) {
- ALOGV("channel %d stolen - event queued for channel %d", channelID(), nextChannelID);
- mNextEvent.set(sample, nextChannelID, leftVolume, rightVolume, priority, loop, rate);
- stop_l();
- return;
- }
-
- // initialize track
- size_t afFrameCount;
- uint32_t afSampleRate;
- audio_stream_type_t streamType =
- AudioSystem::attributesToStreamType(*mSoundPool->attributes());
- if (AudioSystem::getOutputFrameCount(&afFrameCount, streamType) != NO_ERROR) {
- afFrameCount = kDefaultFrameCount;
- }
- if (AudioSystem::getOutputSamplingRate(&afSampleRate, streamType) != NO_ERROR) {
- afSampleRate = kDefaultSampleRate;
- }
- int numChannels = sample->numChannels();
- uint32_t sampleRate = uint32_t(float(sample->sampleRate()) * rate + 0.5);
- size_t frameCount = 0;
-
- if (loop) {
- const audio_format_t format = sample->format();
- const size_t frameSize = audio_is_linear_pcm(format)
- ? numChannels * audio_bytes_per_sample(format) : 1;
- frameCount = sample->size() / frameSize;
- }
-
-#ifndef USE_SHARED_MEM_BUFFER
- uint32_t totalFrames = (kDefaultBufferCount * afFrameCount * sampleRate) / afSampleRate;
- // Ensure minimum audio buffer size in case of short looped sample
- if(frameCount < totalFrames) {
- frameCount = totalFrames;
- }
-#endif
-
- // check if the existing track has the same sample id.
- if (mAudioTrack != 0 && mPrevSampleID == sample->sampleID()) {
- // the sample rate may fail to change if the audio track is a fast track.
- if (mAudioTrack->setSampleRate(sampleRate) == NO_ERROR) {
- newTrack = mAudioTrack;
- ALOGV("reusing track %p for sample %d", mAudioTrack.get(), sample->sampleID());
- }
- }
- if (newTrack == 0) {
- // mToggle toggles each time a track is started on a given channel.
- // The toggle is concatenated with the SoundChannel address and passed to AudioTrack
- // as callback user data. This enables the detection of callbacks received from the old
- // audio track while the new one is being started and avoids processing them with
- // wrong audio audio buffer size (mAudioBufferSize)
- unsigned long toggle = mToggle ^ 1;
- void *userData = (void *)((unsigned long)this | toggle);
- audio_channel_mask_t sampleChannelMask = sample->channelMask();
- // When sample contains a not none channel mask, use it as is.
- // Otherwise, use channel count to calculate channel mask.
- audio_channel_mask_t channelMask = sampleChannelMask != AUDIO_CHANNEL_NONE
- ? sampleChannelMask : audio_channel_out_mask_from_count(numChannels);
-
- // do not create a new audio track if current track is compatible with sample parameters
- #ifdef USE_SHARED_MEM_BUFFER
- newTrack = new AudioTrack(streamType, sampleRate, sample->format(),
- channelMask, sample->getIMemory(), AUDIO_OUTPUT_FLAG_FAST, callback, userData,
- 0 /*default notification frames*/, AUDIO_SESSION_ALLOCATE,
- AudioTrack::TRANSFER_DEFAULT,
- NULL /*offloadInfo*/, -1 /*uid*/, -1 /*pid*/, mSoundPool->attributes());
- #else
- uint32_t bufferFrames = (totalFrames + (kDefaultBufferCount - 1)) / kDefaultBufferCount;
- newTrack = new AudioTrack(streamType, sampleRate, sample->format(),
- channelMask, frameCount, AUDIO_OUTPUT_FLAG_FAST, callback, userData,
- bufferFrames, AUDIO_SESSION_ALLOCATE, AudioTrack::TRANSFER_DEFAULT,
- NULL /*offloadInfo*/, -1 /*uid*/, -1 /*pid*/, mSoundPool->attributes());
- #endif
- oldTrack = mAudioTrack;
- status = newTrack->initCheck();
- if (status != NO_ERROR) {
- ALOGE("Error creating AudioTrack");
- // newTrack goes out of scope, so reference count drops to zero
- goto exit;
- }
- // From now on, AudioTrack callbacks received with previous toggle value will be ignored.
- mToggle = toggle;
- mAudioTrack = newTrack;
- ALOGV("using new track %p for sample %d", newTrack.get(), sample->sampleID());
- }
- if (mMuted) {
- newTrack->setVolume(0.0f, 0.0f);
- } else {
- newTrack->setVolume(leftVolume, rightVolume);
- }
- newTrack->setLoop(0, frameCount, loop);
- mPos = 0;
- mSample = sample;
- mChannelID = nextChannelID;
- mPriority = priority;
- mLoop = loop;
- mLeftVolume = leftVolume;
- mRightVolume = rightVolume;
- mNumChannels = numChannels;
- mRate = rate;
- clearNextEvent();
- mState = PLAYING;
- mAudioTrack->start();
- mAudioBufferSize = newTrack->frameCount()*newTrack->frameSize();
- }
-
-exit:
- ALOGV("delete oldTrack %p", oldTrack.get());
- if (status != NO_ERROR) {
- mAudioTrack.clear();
- }
-}
-
-void SoundChannel::nextEvent()
-{
- sp<Sample> sample;
- int nextChannelID;
- float leftVolume;
- float rightVolume;
- int priority;
- int loop;
- float rate;
-
- // check for valid event
- {
- Mutex::Autolock lock(&mLock);
- nextChannelID = mNextEvent.channelID();
- if (nextChannelID == 0) {
- ALOGV("stolen channel has no event");
- return;
- }
-
- sample = mNextEvent.sample();
- leftVolume = mNextEvent.leftVolume();
- rightVolume = mNextEvent.rightVolume();
- priority = mNextEvent.priority();
- loop = mNextEvent.loop();
- rate = mNextEvent.rate();
- }
-
- ALOGV("Starting stolen channel %d -> %d", channelID(), nextChannelID);
- play(sample, nextChannelID, leftVolume, rightVolume, priority, loop, rate);
-}
-
-void SoundChannel::callback(int event, void* user, void *info)
-{
- SoundChannel* channel = static_cast<SoundChannel*>((void *)((unsigned long)user & ~1));
-
- channel->process(event, info, (unsigned long)user & 1);
-}
-
-void SoundChannel::process(int event, void *info, unsigned long toggle)
-{
- //ALOGV("process(%d)", mChannelID);
-
- Mutex::Autolock lock(&mLock);
-
- AudioTrack::Buffer* b = NULL;
- if (event == AudioTrack::EVENT_MORE_DATA) {
- b = static_cast<AudioTrack::Buffer *>(info);
- }
-
- if (mToggle != toggle) {
- ALOGV("process wrong toggle %p channel %d", this, mChannelID);
- if (b != NULL) {
- b->size = 0;
- }
- return;
- }
-
- sp<Sample> sample = mSample;
-
-// ALOGV("SoundChannel::process event %d", event);
-
- if (event == AudioTrack::EVENT_MORE_DATA) {
-
- // check for stop state
- if (b->size == 0) return;
-
- if (mState == IDLE) {
- b->size = 0;
- return;
- }
-
- if (sample != 0) {
- // fill buffer
- uint8_t* q = (uint8_t*) b->i8;
- size_t count = 0;
-
- if (mPos < (int)sample->size()) {
- uint8_t* p = sample->data() + mPos;
- count = sample->size() - mPos;
- if (count > b->size) {
- count = b->size;
- }
- memcpy(q, p, count);
-// ALOGV("fill: q=%p, p=%p, mPos=%u, b->size=%u, count=%d", q, p, mPos, b->size,
-// count);
- } else if (mPos < mAudioBufferSize) {
- count = mAudioBufferSize - mPos;
- if (count > b->size) {
- count = b->size;
- }
- memset(q, 0, count);
-// ALOGV("fill extra: q=%p, mPos=%u, b->size=%u, count=%d", q, mPos, b->size, count);
- }
-
- mPos += count;
- b->size = count;
- //ALOGV("buffer=%p, [0]=%d", b->i16, b->i16[0]);
- }
- } else if (event == AudioTrack::EVENT_UNDERRUN || event == AudioTrack::EVENT_BUFFER_END) {
- ALOGV("process %p channel %d event %s",
- this, mChannelID, (event == AudioTrack::EVENT_UNDERRUN) ? "UNDERRUN" :
- "BUFFER_END");
- // Only BUFFER_END should happen as we use static tracks.
- setVolume_l(0.f, 0.f); // set volume to 0 to indicate no need to ramp volume down.
- mSoundPool->addToStopList(this);
- } else if (event == AudioTrack::EVENT_LOOP_END) {
- ALOGV("End loop %p channel %d", this, mChannelID);
- } else if (event == AudioTrack::EVENT_NEW_IAUDIOTRACK) {
- ALOGV("process %p channel %d NEW_IAUDIOTRACK", this, mChannelID);
- } else {
- ALOGW("SoundChannel::process unexpected event %d", event);
- }
-}
-
-
-// call with lock held
-bool SoundChannel::doStop_l()
-{
- if (mState != IDLE) {
- ALOGV("stop");
- if (mLeftVolume != 0.f || mRightVolume != 0.f) {
- setVolume_l(0.f, 0.f);
- if (mSoundPool->attributes()->usage != AUDIO_USAGE_GAME) {
- // Since we're forcibly halting the previously playing content,
- // we sleep here to ensure the volume is ramped down before we stop the track.
- // Ideally the sleep time is the mixer period, or an approximation thereof
- // (Fast vs Normal tracks are different).
- ALOGV("sleeping: ChannelID:%d SampleID:%d", mChannelID, mSample->sampleID());
- std::this_thread::sleep_for(std::chrono::milliseconds(20));
- }
- }
- mAudioTrack->stop();
- mPrevSampleID = mSample->sampleID();
- mSample.clear();
- mState = IDLE;
- mPriority = IDLE_PRIORITY;
- return true;
- }
- return false;
-}
-
-// call with lock held and sound pool lock held
-void SoundChannel::stop_l()
-{
- if (doStop_l()) {
- mSoundPool->done_l(this);
- }
-}
-
-// call with sound pool lock held
-void SoundChannel::stop()
-{
- bool stopped;
- {
- Mutex::Autolock lock(&mLock);
- stopped = doStop_l();
- }
-
- if (stopped) {
- mSoundPool->done_l(this);
- }
-}
-
-//FIXME: Pause is a little broken right now
-void SoundChannel::pause()
-{
- Mutex::Autolock lock(&mLock);
- if (mState == PLAYING) {
- ALOGV("pause track");
- mState = PAUSED;
- mAudioTrack->pause();
- }
-}
-
-void SoundChannel::autoPause()
-{
- Mutex::Autolock lock(&mLock);
- if (mState == PLAYING) {
- ALOGV("pause track");
- mState = PAUSED;
- mAutoPaused = true;
- mAudioTrack->pause();
- }
-}
-
-void SoundChannel::resume()
-{
- Mutex::Autolock lock(&mLock);
- if (mState == PAUSED) {
- ALOGV("resume track");
- mState = PLAYING;
- mAutoPaused = false;
- mAudioTrack->start();
- }
-}
-
-void SoundChannel::autoResume()
-{
- Mutex::Autolock lock(&mLock);
- if (mAutoPaused && (mState == PAUSED)) {
- ALOGV("resume track");
- mState = PLAYING;
- mAutoPaused = false;
- mAudioTrack->start();
- }
-}
-
-void SoundChannel::setRate(float rate)
-{
- Mutex::Autolock lock(&mLock);
- if (mAudioTrack != NULL && mSample != 0) {
- uint32_t sampleRate = uint32_t(float(mSample->sampleRate()) * rate + 0.5);
- mAudioTrack->setSampleRate(sampleRate);
- mRate = rate;
- }
-}
-
-// call with lock held
-void SoundChannel::setVolume_l(float leftVolume, float rightVolume)
-{
- mLeftVolume = leftVolume;
- mRightVolume = rightVolume;
- if (mAudioTrack != NULL && !mMuted)
- mAudioTrack->setVolume(leftVolume, rightVolume);
-}
-
-void SoundChannel::setVolume(float leftVolume, float rightVolume)
-{
- Mutex::Autolock lock(&mLock);
- setVolume_l(leftVolume, rightVolume);
-}
-
-void SoundChannel::mute(bool muting)
-{
- Mutex::Autolock lock(&mLock);
- mMuted = muting;
- if (mAudioTrack != NULL) {
- if (mMuted) {
- mAudioTrack->setVolume(0.0f, 0.0f);
- } else {
- mAudioTrack->setVolume(mLeftVolume, mRightVolume);
- }
- }
-}
-
-void SoundChannel::setLoop(int loop)
-{
- Mutex::Autolock lock(&mLock);
- if (mAudioTrack != NULL && mSample != 0) {
- uint32_t loopEnd = mSample->size()/mNumChannels/
- ((mSample->format() == AUDIO_FORMAT_PCM_16_BIT) ? sizeof(int16_t) : sizeof(uint8_t));
- mAudioTrack->setLoop(0, loopEnd, loop);
- mLoop = loop;
- }
-}
-
-SoundChannel::~SoundChannel()
-{
- ALOGV("SoundChannel destructor %p", this);
- {
- Mutex::Autolock lock(&mLock);
- clearNextEvent();
- doStop_l();
- }
- // do not call AudioTrack destructor with mLock held as it will wait for the AudioTrack
- // callback thread to exit which may need to execute process() and acquire the mLock.
- mAudioTrack.clear();
-}
-
-void SoundChannel::dump()
-{
- ALOGV("mState = %d mChannelID=%d, mNumChannels=%d, mPos = %d, mPriority=%d, mLoop=%d",
- mState, mChannelID, mNumChannels, mPos, mPriority, mLoop);
-}
-
-void SoundEvent::set(const sp<Sample>& sample, int channelID, float leftVolume,
- float rightVolume, int priority, int loop, float rate)
-{
- mSample = sample;
- mChannelID = channelID;
- mLeftVolume = leftVolume;
- mRightVolume = rightVolume;
- mPriority = priority;
- mLoop = loop;
- mRate =rate;
+ ALOGV("%s()", __func__);
+ auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+ return mSoundManager.getUserData();
}
} // end namespace android
diff --git a/media/jni/soundpool/SoundPool.h b/media/jni/soundpool/SoundPool.h
index 01e4faa..d5b16ef 100644
--- a/media/jni/soundpool/SoundPool.h
+++ b/media/jni/soundpool/SoundPool.h
@@ -14,227 +14,59 @@
* limitations under the License.
*/
-#ifndef SOUNDPOOL_H_
-#define SOUNDPOOL_H_
+#pragma once
-#include <utils/threads.h>
-#include <utils/List.h>
-#include <utils/Vector.h>
-#include <utils/KeyedVector.h>
-#include <media/AudioTrack.h>
-#include <binder/MemoryHeapBase.h>
-#include <binder/MemoryBase.h>
+#include "SoundManager.h"
+#include "StreamManager.h"
namespace android {
-static const int IDLE_PRIORITY = -1;
-
-// forward declarations
-class SoundEvent;
-class SoundPoolThread;
-class SoundPool;
-
-// for queued events
-class SoundPoolEvent {
-public:
- explicit SoundPoolEvent(int msg, int arg1=0, int arg2=0) :
- mMsg(msg), mArg1(arg1), mArg2(arg2) {}
- int mMsg;
- int mArg1;
- int mArg2;
- enum MessageType { INVALID, SAMPLE_LOADED };
-};
-
-// callback function prototype
-typedef void SoundPoolCallback(SoundPoolEvent event, SoundPool* soundPool, void* user);
-
-// tracks samples used by application
-class Sample : public RefBase {
-public:
- enum sample_state { UNLOADED, LOADING, READY, UNLOADING };
- Sample(int sampleID, int fd, int64_t offset, int64_t length);
- ~Sample();
- int sampleID() { return mSampleID; }
- int numChannels() { return mNumChannels; }
- int sampleRate() { return mSampleRate; }
- audio_format_t format() { return mFormat; }
- audio_channel_mask_t channelMask() { return mChannelMask; }
- size_t size() { return mSize; }
- int state() { return mState; }
- uint8_t* data() { return static_cast<uint8_t*>(mData->unsecurePointer()); }
- status_t doLoad();
- void startLoad() { mState = LOADING; }
- sp<IMemory> getIMemory() { return mData; }
-
-private:
- void init();
-
- size_t mSize;
- volatile int32_t mRefCount;
- uint16_t mSampleID;
- uint16_t mSampleRate;
- uint8_t mState;
- uint8_t mNumChannels;
- audio_format_t mFormat;
- audio_channel_mask_t mChannelMask;
- int mFd;
- int64_t mOffset;
- int64_t mLength;
- sp<IMemory> mData;
- sp<MemoryHeapBase> mHeap;
-};
-
-// stores pending events for stolen channels
-class SoundEvent
-{
-public:
- SoundEvent() : mChannelID(0), mLeftVolume(0), mRightVolume(0),
- mPriority(IDLE_PRIORITY), mLoop(0), mRate(0) {}
- void set(const sp<Sample>& sample, int channelID, float leftVolume,
- float rightVolume, int priority, int loop, float rate);
- sp<Sample> sample() { return mSample; }
- int channelID() { return mChannelID; }
- float leftVolume() { return mLeftVolume; }
- float rightVolume() { return mRightVolume; }
- int priority() { return mPriority; }
- int loop() { return mLoop; }
- float rate() { return mRate; }
- void clear() { mChannelID = 0; mSample.clear(); }
-
-protected:
- sp<Sample> mSample;
- int mChannelID;
- float mLeftVolume;
- float mRightVolume;
- int mPriority;
- int mLoop;
- float mRate;
-};
-
-// for channels aka AudioTracks
-class SoundChannel : public SoundEvent {
-public:
- enum state { IDLE, RESUMING, STOPPING, PAUSED, PLAYING };
- SoundChannel() : mState(IDLE), mNumChannels(1),
- mPos(0), mToggle(0), mAutoPaused(false), mMuted(false) {}
- ~SoundChannel();
- void init(SoundPool* soundPool);
- void play(const sp<Sample>& sample, int channelID, float leftVolume, float rightVolume,
- int priority, int loop, float rate);
- void setVolume_l(float leftVolume, float rightVolume);
- void setVolume(float leftVolume, float rightVolume);
- void mute(bool muting);
- void stop_l();
- void stop();
- void pause();
- void autoPause();
- void resume();
- void autoResume();
- void setRate(float rate);
- int state() { return mState; }
- void setPriority(int priority) { mPriority = priority; }
- void setLoop(int loop);
- int numChannels() { return mNumChannels; }
- void clearNextEvent() { mNextEvent.clear(); }
- void nextEvent();
- int nextChannelID() { return mNextEvent.channelID(); }
- void dump();
- int getPrevSampleID(void) { return mPrevSampleID; }
-
-private:
- static void callback(int event, void* user, void *info);
- void process(int event, void *info, unsigned long toggle);
- bool doStop_l();
-
- SoundPool* mSoundPool;
- sp<AudioTrack> mAudioTrack;
- SoundEvent mNextEvent;
- Mutex mLock;
- int mState;
- int mNumChannels;
- int mPos;
- int mAudioBufferSize;
- unsigned long mToggle;
- bool mAutoPaused;
- int mPrevSampleID;
- bool mMuted;
-};
-
-// application object for managing a pool of sounds
+/**
+ * Native class for Java SoundPool, manages a pool of sounds.
+ *
+ * See the Android SoundPool Java documentation for description of valid values.
+ * https://developer.android.com/reference/android/media/SoundPool
+ */
class SoundPool {
- friend class SoundPoolThread;
- friend class SoundChannel;
public:
- SoundPool(int maxChannels, const audio_attributes_t* pAttributes);
+ SoundPool(int32_t maxStreams, const audio_attributes_t* attributes);
~SoundPool();
- int load(int fd, int64_t offset, int64_t length, int priority);
- bool unload(int sampleID);
- int play(int sampleID, float leftVolume, float rightVolume, int priority,
- int loop, float rate);
- void pause(int channelID);
- void mute(bool muting);
+
+ // SoundPool Java API support
+ int32_t load(int fd, int64_t offset, int64_t length, int32_t priority);
+ bool unload(int32_t soundID);
+ int32_t play(int32_t soundID, float leftVolume, float rightVolume, int32_t priority,
+ int32_t loop, float rate);
+ void pause(int32_t streamID);
void autoPause();
- void resume(int channelID);
+ void resume(int32_t streamID);
void autoResume();
- void stop(int channelID);
- void setVolume(int channelID, float leftVolume, float rightVolume);
- void setPriority(int channelID, int priority);
- void setLoop(int channelID, int loop);
- void setRate(int channelID, float rate);
- const audio_attributes_t* attributes() { return &mAttributes; }
-
- // called from SoundPoolThread
- void sampleLoaded(int sampleID);
- sp<Sample> findSample(int sampleID);
-
- // called from AudioTrack thread
- void done_l(SoundChannel* channel);
-
- // callback function
+ void stop(int32_t streamID);
+ void setVolume(int32_t streamID, float leftVolume, float rightVolume);
+ void setPriority(int32_t streamID, int32_t priority);
+ void setLoop(int32_t streamID, int32_t loop);
+ void setRate(int32_t streamID, float rate);
void setCallback(SoundPoolCallback* callback, void* user);
- void* getUserData() { return mUserData; }
+ void* getUserData() const;
+
+ // not exposed in the public Java API, used for internal playerSetVolume() muting.
+ void mute(bool muting);
private:
- SoundPool() {} // no default constructor
- bool startThreads();
- sp<Sample> findSample_l(int sampleID);
- SoundChannel* findChannel (int channelID);
- SoundChannel* findNextChannel (int channelID);
- SoundChannel* allocateChannel_l(int priority, int sampleID);
- void moveToFront_l(SoundChannel* channel);
- void notify(SoundPoolEvent event);
- void dump();
- // restart thread
- void addToRestartList(SoundChannel* channel);
- void addToStopList(SoundChannel* channel);
- static int beginThread(void* arg);
- int run();
- void quit();
+ // Constructor initialized variables
+ // Can access without lock as they are internally locked,
+ // though care needs to be taken that the final result composed of
+ // individually consistent actions are consistent.
+ soundpool::SoundManager mSoundManager;
+ soundpool::StreamManager mStreamManager;
- Mutex mLock;
- Mutex mRestartLock;
- Condition mCondition;
- SoundPoolThread* mDecodeThread;
- SoundChannel* mChannelPool;
- List<SoundChannel*> mChannels;
- List<SoundChannel*> mRestart;
- List<SoundChannel*> mStop;
- DefaultKeyedVector< int, sp<Sample> > mSamples;
- int mMaxChannels;
- audio_attributes_t mAttributes;
- int mAllocated;
- int mNextSampleID;
- int mNextChannelID;
- bool mQuit;
- bool mMuted;
-
- // callback
- Mutex mCallbackLock;
- SoundPoolCallback* mCallback;
- void* mUserData;
+ // mApiLock serializes SoundPool application calls (configurable by kUseApiLock).
+ // It only locks at the SoundPool layer and not below. At this level,
+ // mApiLock is only required for autoPause() and autoResume() to prevent zippering
+ // of the individual pauses and resumes, and mute() for self-interaction with itself.
+ // It is optional for all other apis.
+ mutable std::mutex mApiLock;
};
} // end namespace android
-
-#endif /*SOUNDPOOL_H_*/
diff --git a/media/jni/soundpool/SoundPoolThread.cpp b/media/jni/soundpool/SoundPoolThread.cpp
deleted file mode 100644
index ba3b482..0000000
--- a/media/jni/soundpool/SoundPoolThread.cpp
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2007 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.
- */
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "SoundPoolThread"
-#include "utils/Log.h"
-
-#include "SoundPoolThread.h"
-
-namespace android {
-
-void SoundPoolThread::write(SoundPoolMsg msg) {
- Mutex::Autolock lock(&mLock);
- while (mMsgQueue.size() >= maxMessages) {
- mCondition.wait(mLock);
- }
-
- // if thread is quitting, don't add to queue
- if (mRunning) {
- mMsgQueue.push(msg);
- mCondition.signal();
- }
-}
-
-const SoundPoolMsg SoundPoolThread::read() {
- Mutex::Autolock lock(&mLock);
- while (mMsgQueue.size() == 0) {
- mCondition.wait(mLock);
- }
- SoundPoolMsg msg = mMsgQueue[0];
- mMsgQueue.removeAt(0);
- mCondition.signal();
- return msg;
-}
-
-void SoundPoolThread::quit() {
- Mutex::Autolock lock(&mLock);
- if (mRunning) {
- mRunning = false;
- mMsgQueue.clear();
- mMsgQueue.push(SoundPoolMsg(SoundPoolMsg::KILL, 0));
- mCondition.signal();
- mCondition.wait(mLock);
- }
- ALOGV("return from quit");
-}
-
-SoundPoolThread::SoundPoolThread(SoundPool* soundPool) :
- mSoundPool(soundPool)
-{
- mMsgQueue.setCapacity(maxMessages);
- if (createThreadEtc(beginThread, this, "SoundPoolThread")) {
- mRunning = true;
- }
-}
-
-SoundPoolThread::~SoundPoolThread()
-{
- quit();
-}
-
-int SoundPoolThread::beginThread(void* arg) {
- ALOGV("beginThread");
- SoundPoolThread* soundPoolThread = (SoundPoolThread*)arg;
- return soundPoolThread->run();
-}
-
-int SoundPoolThread::run() {
- ALOGV("run");
- for (;;) {
- SoundPoolMsg msg = read();
- ALOGV("Got message m=%d, mData=%d", msg.mMessageType, msg.mData);
- switch (msg.mMessageType) {
- case SoundPoolMsg::KILL:
- ALOGV("goodbye");
- return NO_ERROR;
- case SoundPoolMsg::LOAD_SAMPLE:
- doLoadSample(msg.mData);
- break;
- default:
- ALOGW("run: Unrecognized message %d\n",
- msg.mMessageType);
- break;
- }
- }
-}
-
-void SoundPoolThread::loadSample(int sampleID) {
- write(SoundPoolMsg(SoundPoolMsg::LOAD_SAMPLE, sampleID));
-}
-
-void SoundPoolThread::doLoadSample(int sampleID) {
- sp <Sample> sample = mSoundPool->findSample(sampleID);
- status_t status = -1;
- if (sample != 0) {
- status = sample->doLoad();
- }
- mSoundPool->notify(SoundPoolEvent(SoundPoolEvent::SAMPLE_LOADED, sampleID, status));
-}
-
-} // end namespace android
diff --git a/media/jni/soundpool/SoundPoolThread.h b/media/jni/soundpool/SoundPoolThread.h
deleted file mode 100644
index 7b3e1dd..0000000
--- a/media/jni/soundpool/SoundPoolThread.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2007 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.
- */
-
-#ifndef SOUNDPOOLTHREAD_H_
-#define SOUNDPOOLTHREAD_H_
-
-#include <utils/threads.h>
-#include <utils/Vector.h>
-#include <media/AudioTrack.h>
-
-#include "SoundPool.h"
-
-namespace android {
-
-class SoundPoolMsg {
-public:
- enum MessageType { INVALID, KILL, LOAD_SAMPLE };
- SoundPoolMsg() : mMessageType(INVALID), mData(0) {}
- SoundPoolMsg(MessageType MessageType, int data) :
- mMessageType(MessageType), mData(data) {}
- uint16_t mMessageType;
- uint16_t mData;
-};
-
-/*
- * This class handles background requests from the SoundPool
- */
-class SoundPoolThread {
-public:
- explicit SoundPoolThread(SoundPool* SoundPool);
- ~SoundPoolThread();
- void loadSample(int sampleID);
- void quit();
- void write(SoundPoolMsg msg);
-
-private:
- static const size_t maxMessages = 128;
-
- static int beginThread(void* arg);
- int run();
- void doLoadSample(int sampleID);
- const SoundPoolMsg read();
-
- Mutex mLock;
- Condition mCondition;
- Vector<SoundPoolMsg> mMsgQueue;
- SoundPool* mSoundPool;
- bool mRunning;
-};
-
-} // end namespace android
-
-#endif /*SOUNDPOOLTHREAD_H_*/
diff --git a/media/jni/soundpool/Stream.cpp b/media/jni/soundpool/Stream.cpp
new file mode 100644
index 0000000..e7d4d90
--- /dev/null
+++ b/media/jni/soundpool/Stream.cpp
@@ -0,0 +1,448 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SoundPool::Stream"
+#include <utils/Log.h>
+
+#include "Stream.h"
+
+#include "StreamManager.h"
+
+namespace android::soundpool {
+
+Stream::~Stream()
+{
+ ALOGV("%s(%p)", __func__, this);
+}
+
+void Stream::autoPause()
+{
+ std::lock_guard lock(mLock);
+ if (mState == PLAYING) {
+ ALOGV("%s: track streamID: %d", __func__, (int)mStreamID);
+ mState = PAUSED;
+ mAutoPaused = true;
+ if (mAudioTrack != nullptr) {
+ mAudioTrack->pause();
+ }
+ }
+}
+
+void Stream::autoResume()
+{
+ std::lock_guard lock(mLock);
+ if (mAutoPaused) {
+ if (mState == PAUSED) {
+ ALOGV("%s: track streamID: %d", __func__, (int)mStreamID);
+ mState = PLAYING;
+ if (mAudioTrack != nullptr) {
+ mAudioTrack->start();
+ }
+ }
+ mAutoPaused = false; // New for R: always reset autopause (consistent with API spec).
+ }
+}
+
+void Stream::mute(bool muting)
+{
+ std::lock_guard lock(mLock);
+ mMuted = muting;
+ if (mAudioTrack != nullptr) {
+ if (mMuted) {
+ mAudioTrack->setVolume(0.0f, 0.0f);
+ } else {
+ mAudioTrack->setVolume(mLeftVolume, mRightVolume);
+ }
+ }
+}
+
+void Stream::pause(int32_t streamID)
+{
+ std::lock_guard lock(mLock);
+ if (streamID == mStreamID) {
+ if (mState == PLAYING) {
+ ALOGV("%s: track streamID: %d", __func__, streamID);
+ mState = PAUSED;
+ if (mAudioTrack != nullptr) {
+ mAudioTrack->pause();
+ }
+ }
+ }
+}
+
+void Stream::resume(int32_t streamID)
+{
+ std::lock_guard lock(mLock);
+ if (streamID == mStreamID) {
+ if (mState == PAUSED) {
+ ALOGV("%s: track streamID: %d", __func__, streamID);
+ mState = PLAYING;
+ if (mAudioTrack != nullptr) {
+ mAudioTrack->start();
+ }
+ mAutoPaused = false; // TODO: is this right? (ambiguous per spec), move outside?
+ }
+ }
+}
+
+void Stream::setRate(int32_t streamID, float rate)
+{
+ std::lock_guard lock(mLock);
+ if (streamID == mStreamID) {
+ mRate = rate;
+ if (mAudioTrack != nullptr && mSound != nullptr) {
+ const uint32_t sampleRate = uint32_t(float(mSound->getSampleRate()) * rate + 0.5);
+ mAudioTrack->setSampleRate(sampleRate);
+ }
+ }
+}
+
+void Stream::setVolume_l(float leftVolume, float rightVolume)
+{
+ mLeftVolume = leftVolume;
+ mRightVolume = rightVolume;
+ if (mAudioTrack != nullptr && !mMuted) {
+ mAudioTrack->setVolume(leftVolume, rightVolume);
+ }
+}
+
+void Stream::setVolume(int32_t streamID, float leftVolume, float rightVolume)
+{
+ std::lock_guard lock(mLock);
+ if (streamID == mStreamID) {
+ setVolume_l(leftVolume, rightVolume);
+ }
+}
+
+void Stream::setPriority(int32_t streamID, int32_t priority)
+{
+ std::lock_guard lock(mLock);
+ if (streamID == mStreamID) {
+ mPriority = priority;
+ }
+}
+
+void Stream::setLoop(int32_t streamID, int32_t loop)
+{
+ std::lock_guard lock(mLock);
+ if (streamID == mStreamID) {
+ if (mAudioTrack != nullptr && mSound != nullptr) {
+ const uint32_t loopEnd = mSound->getSizeInBytes() / mSound->getChannelCount() /
+ (mSound->getFormat() == AUDIO_FORMAT_PCM_16_BIT
+ ? sizeof(int16_t) : sizeof(uint8_t));
+ mAudioTrack->setLoop(0, loopEnd, loop);
+ }
+ mLoop = loop;
+ }
+}
+
+void Stream::setPlay(
+ int32_t streamID, const std::shared_ptr<Sound> &sound, int32_t soundID,
+ float leftVolume, float rightVolume, int32_t priority, int32_t loop, float rate)
+{
+ std::lock_guard lock(mLock);
+ // We must be idle, or we must be repurposing a pending Stream.
+ LOG_ALWAYS_FATAL_IF(mState != IDLE && mAudioTrack != nullptr, "State %d must be IDLE", mState);
+ mSound = sound;
+ mSoundID = soundID;
+ mLeftVolume = leftVolume;
+ mRightVolume = rightVolume;
+ mPriority = priority;
+ mLoop = loop;
+ mRate = rate;
+ mState = PLAYING;
+ mAutoPaused = false; // New for R (consistent with Java API spec).
+ mStreamID = streamID; // prefer this to be the last, as it is an atomic sync point
+}
+
+void Stream::setStopTimeNs(int64_t stopTimeNs)
+{
+ std::lock_guard lock(mLock);
+ mStopTimeNs = stopTimeNs;
+}
+
+bool Stream::requestStop(int32_t streamID)
+{
+ std::lock_guard lock(mLock);
+ if (streamID == mStreamID) {
+ if (mAudioTrack != nullptr) {
+ if (mState == PLAYING && !mMuted && (mLeftVolume != 0.f || mRightVolume != 0.f)) {
+ setVolume_l(0.f, 0.f);
+ mStopTimeNs = systemTime() + kStopWaitTimeNs;
+ } else {
+ mStopTimeNs = systemTime();
+ }
+ return true; // must be queued on the restart list.
+ }
+ stop_l();
+ }
+ return false;
+}
+
+void Stream::stop()
+{
+ std::lock_guard lock(mLock);
+ stop_l();
+}
+
+void Stream::stop_l()
+{
+ if (mState != IDLE) {
+ if (mAudioTrack != nullptr) {
+ mAudioTrack->stop();
+ }
+ mSound.reset();
+ mState = IDLE;
+ }
+}
+
+void Stream::clearAudioTrack()
+{
+ // This will invoke the destructor which waits for the AudioTrack thread to join,
+ // and is currently the only safe way to ensure there are no callbacks afterwards.
+ mAudioTrack.clear();
+}
+
+Stream* Stream::getPairStream() const
+{
+ return mStreamManager->getPairStream(this);
+}
+
+Stream* Stream::playPairStream() {
+ Stream* pairStream = getPairStream();
+ LOG_ALWAYS_FATAL_IF(pairStream == nullptr, "No pair stream!");
+ sp<AudioTrack> releaseTracks[2];
+ {
+ // TODO: Do we really want to force a simultaneous synchronization between
+ // the stream and its pair?
+
+ // note locking order - the paired stream is obtained before the queued stream.
+ // we can invert the locking order, but it is slightly more optimal to do it this way.
+ std::lock_guard lockp(pairStream->mLock);
+ if (pairStream->mSound == nullptr) {
+ return nullptr; // no pair sound
+ }
+ {
+ std::lock_guard lock(mLock);
+ LOG_ALWAYS_FATAL_IF(mState != IDLE, "State: %d must be IDLE", mState);
+ // TODO: do we want a specific set() here?
+ pairStream->mAudioTrack = mAudioTrack;
+ pairStream->mSoundID = mSoundID; // optimization to reuse AudioTrack.
+ pairStream->mToggle = mToggle;
+ pairStream->mAutoPaused = mAutoPaused; // save autopause state
+ pairStream->mMuted = mMuted;
+ mAudioTrack.clear(); // the pair owns the audiotrack.
+ mSound.reset();
+ mSoundID = 0;
+ }
+ // TODO: do we need a specific play_l() anymore?
+ const int pairState = pairStream->mState;
+ pairStream->play_l(pairStream->mSound, pairStream->mStreamID,
+ pairStream->mLeftVolume, pairStream->mRightVolume, pairStream->mPriority,
+ pairStream->mLoop, pairStream->mRate, releaseTracks);
+ if (pairStream->mState == IDLE) {
+ return nullptr; // AudioTrack error
+ }
+ if (pairState == PAUSED) { // reestablish pause
+ pairStream->mState = PAUSED;
+ pairStream->mAudioTrack->pause();
+ }
+ }
+ // release tracks outside of Stream lock
+ return pairStream;
+}
+
+void Stream::play_l(const std::shared_ptr<Sound>& sound, int32_t nextStreamID,
+ float leftVolume, float rightVolume, int32_t priority, int32_t loop, float rate,
+ sp<AudioTrack> releaseTracks[2])
+{
+ // These tracks are released without the lock.
+ sp<AudioTrack> &oldTrack = releaseTracks[0];
+ sp<AudioTrack> &newTrack = releaseTracks[1];
+ status_t status = NO_ERROR;
+
+ {
+ ALOGV("%s(%p)(soundID=%d, streamID=%d, leftVolume=%f, rightVolume=%f,"
+ " priority=%d, loop=%d, rate=%f)",
+ __func__, this, sound->getSoundID(), nextStreamID, leftVolume, rightVolume,
+ priority, loop, rate);
+
+ // initialize track
+ const audio_stream_type_t streamType =
+ AudioSystem::attributesToStreamType(*mStreamManager->getAttributes());
+ const int32_t channelCount = sound->getChannelCount();
+ const uint32_t sampleRate = uint32_t(float(sound->getSampleRate()) * rate + 0.5);
+ size_t frameCount = 0;
+
+ if (loop) {
+ const audio_format_t format = sound->getFormat();
+ const size_t frameSize = audio_is_linear_pcm(format)
+ ? channelCount * audio_bytes_per_sample(format) : 1;
+ frameCount = sound->getSizeInBytes() / frameSize;
+ }
+
+ // check if the existing track has the same sound id.
+ if (mAudioTrack != nullptr && mSoundID == sound->getSoundID()) {
+ // the sample rate may fail to change if the audio track is a fast track.
+ if (mAudioTrack->setSampleRate(sampleRate) == NO_ERROR) {
+ newTrack = mAudioTrack;
+ ALOGV("%s: reusing track %p for sound %d",
+ __func__, mAudioTrack.get(), sound->getSoundID());
+ }
+ }
+ if (newTrack == 0) {
+ // mToggle toggles each time a track is started on a given stream.
+ // The toggle is concatenated with the Stream address and passed to AudioTrack
+ // as callback user data. This enables the detection of callbacks received from the old
+ // audio track while the new one is being started and avoids processing them with
+ // wrong audio audio buffer size (mAudioBufferSize)
+ auto toggle = mToggle ^ 1;
+ void* userData = (void*)((uintptr_t)this | toggle);
+ audio_channel_mask_t soundChannelMask = sound->getChannelMask();
+ // When sound contains a valid channel mask, use it as is.
+ // Otherwise, use stream count to calculate channel mask.
+ audio_channel_mask_t channelMask = soundChannelMask != AUDIO_CHANNEL_NONE
+ ? soundChannelMask : audio_channel_out_mask_from_count(channelCount);
+
+ // do not create a new audio track if current track is compatible with sound parameters
+
+ newTrack = new AudioTrack(streamType, sampleRate, sound->getFormat(),
+ channelMask, sound->getIMemory(), AUDIO_OUTPUT_FLAG_FAST,
+ staticCallback, userData,
+ 0 /*default notification frames*/, AUDIO_SESSION_ALLOCATE,
+ AudioTrack::TRANSFER_DEFAULT,
+ nullptr /*offloadInfo*/, -1 /*uid*/, -1 /*pid*/,
+ mStreamManager->getAttributes());
+
+ oldTrack = mAudioTrack;
+ status = newTrack->initCheck();
+ if (status != NO_ERROR) {
+ ALOGE("%s: error creating AudioTrack", __func__);
+ // newTrack goes out of scope, so reference count drops to zero
+ goto exit;
+ }
+ // From now on, AudioTrack callbacks received with previous toggle value will be ignored.
+ mToggle = toggle;
+ mAudioTrack = newTrack;
+ ALOGV("%s: using new track %p for sound %d",
+ __func__, newTrack.get(), sound->getSoundID());
+ }
+ if (mMuted) {
+ newTrack->setVolume(0.0f, 0.0f);
+ } else {
+ newTrack->setVolume(leftVolume, rightVolume);
+ }
+ newTrack->setLoop(0, frameCount, loop);
+ mAudioTrack->start();
+ mSound = sound;
+ mSoundID = sound->getSoundID();
+ mPriority = priority;
+ mLoop = loop;
+ mLeftVolume = leftVolume;
+ mRightVolume = rightVolume;
+ mRate = rate;
+ mState = PLAYING;
+ mStopTimeNs = 0;
+ mStreamID = nextStreamID; // prefer this to be the last, as it is an atomic sync point
+ }
+
+exit:
+ ALOGV("%s: delete oldTrack %p", __func__, oldTrack.get());
+ if (status != NO_ERROR) {
+ // TODO: should we consider keeping the soundID if the old track is OK?
+ // Do not attempt to restart this track (should we remove the stream id?)
+ mState = IDLE;
+ mSoundID = 0;
+ mSound.reset();
+ mAudioTrack.clear(); // actual release from releaseTracks[]
+ }
+}
+
+/* static */
+void Stream::staticCallback(int event, void* user, void* info)
+{
+ const uintptr_t userAsInt = (uintptr_t)user;
+ Stream* stream = reinterpret_cast<Stream*>(userAsInt & ~1);
+ stream->callback(event, info, userAsInt & 1, 0 /* tries */);
+}
+
+void Stream::callback(int event, void* info, int toggle, int tries)
+{
+ ALOGV("%s streamID %d", __func__, (int)mStreamID);
+ int32_t activeStreamIDToRestart = 0;
+ {
+ std::unique_lock lock(mLock);
+
+ if (mAudioTrack == nullptr) {
+ // The AudioTrack is either with this stream or its pair.
+ // if this swaps a few times, the toggle is bound to be wrong, so we fail then.
+ //
+ // TODO: Modify AudioTrack callbacks to avoid the hacky toggle and retry
+ // logic here.
+ if (tries < 3) {
+ lock.unlock();
+ getPairStream()->callback(event, info, toggle, tries + 1);
+ } else {
+ ALOGW("%s streamID %d cannot find track", __func__, (int)mStreamID);
+ }
+ return;
+ }
+ if (mToggle != toggle) {
+ ALOGD("%s streamID %d wrong toggle", __func__, (int)mStreamID);
+ return;
+ }
+ switch (event) {
+ case AudioTrack::EVENT_MORE_DATA:
+ ALOGW("%s streamID %d Invalid EVENT_MORE_DATA for static track",
+ __func__, (int)mStreamID);
+ break;
+ case AudioTrack::EVENT_UNDERRUN:
+ ALOGW("%s streamID %d Invalid EVENT_UNDERRUN for static track",
+ __func__, (int)mStreamID);
+ break;
+ case AudioTrack::EVENT_BUFFER_END:
+ ALOGV("%s streamID %d EVENT_BUFFER_END", __func__, (int)mStreamID);
+ if (mState != IDLE) {
+ activeStreamIDToRestart = mStreamID;
+ mStopTimeNs = systemTime();
+ }
+ break;
+ case AudioTrack::EVENT_LOOP_END:
+ ALOGV("%s streamID %d EVENT_LOOP_END", __func__, (int)mStreamID);
+ break;
+ case AudioTrack::EVENT_NEW_IAUDIOTRACK:
+ ALOGV("%s streamID %d NEW_IAUDIOTRACK", __func__, (int)mStreamID);
+ break;
+ default:
+ ALOGW("%s streamID %d Invalid event %d", __func__, (int)mStreamID, event);
+ break;
+ }
+ } // lock ends here. This is on the callback thread, no need to be precise.
+ if (activeStreamIDToRestart > 0) {
+ // Restart only if a particular streamID is still current and active.
+ ALOGV("%s: moveToRestartQueue %d", __func__, activeStreamIDToRestart);
+ mStreamManager->moveToRestartQueue(this, activeStreamIDToRestart);
+ }
+}
+
+void Stream::dump() const
+{
+ ALOGV("mPairStream=%p, mState=%d, mStreamID=%d, mSoundID=%d, mPriority=%d, mLoop=%d",
+ getPairStream(), mState, (int)mStreamID, mSoundID, mPriority, mLoop);
+}
+
+} // namespace android::soundpool
diff --git a/media/jni/soundpool/Stream.h b/media/jni/soundpool/Stream.h
new file mode 100644
index 0000000..82d2690
--- /dev/null
+++ b/media/jni/soundpool/Stream.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#pragma once
+
+#include "Sound.h"
+
+#include <audio_utils/clock.h>
+#include <media/AudioTrack.h>
+
+namespace android::soundpool {
+
+// This is the amount of time to wait after stop is called when stealing an
+// AudioTrack to allow the sound to ramp down. If this is 0, glitches
+// may occur when stealing an AudioTrack.
+inline constexpr int64_t kStopWaitTimeNs = 20 * NANOS_PER_MILLISECOND;
+
+inline constexpr size_t kCacheLineSize = 64; /* std::hardware_constructive_interference_size */
+
+class StreamManager; // forward decl
+
+/**
+ * A Stream is associated with a StreamID exposed to the app to play a Sound.
+ *
+ * The Stream uses monitor locking strategy on mLock.
+ * https://en.wikipedia.org/wiki/Monitor_(synchronization)
+ *
+ * where public methods are guarded by a lock (as needed)
+ *
+ * For Java equivalent APIs, see
+ * https://developer.android.com/reference/android/media/SoundPool
+ *
+ * Streams are paired by the StreamManager, so one stream in the pair may be "stopping"
+ * while the other stream of the pair has been prepared to run
+ * (and the streamID returned to the app) pending its pair to be stopped.
+ * The pair of a Stream may be obtained by calling getPairStream(),
+ * where this->getPairStream()->getPairStream() == this; (pair is a commutative relationship).
+ *
+ * playPairStream() and getPairPriority() access the paired stream.
+ * See also StreamManager.h for details of physical layout implications of paired streams.
+ */
+class alignas(kCacheLineSize) Stream {
+public:
+ enum state { IDLE, PAUSED, PLAYING };
+ // The PAUSED, PLAYING state directly corresponds to the AudioTrack state of an active Stream.
+ //
+ // The IDLE state indicates an inactive Stream. An IDLE Stream may have a non-nullptr
+ // AudioTrack, which may be recycled for use if the SoundID matches the next Stream playback.
+ //
+ // PAUSED -> PLAYING through resume() (see also autoResume())
+ // PLAYING -> PAUSED through pause() (see also autoPause())
+ //
+ // IDLE is the initial state of a Stream and also when a stream becomes inactive.
+ // {PAUSED, PLAYING} -> IDLE through stop() (or if the Sound finishes playing)
+ // IDLE -> PLAYING through play(). (there is no way to start a Stream in paused mode).
+
+ ~Stream();
+ void setStreamManager(StreamManager* streamManager) { // non-nullptr
+ mStreamManager = streamManager; // set in StreamManager constructor, not changed
+ }
+
+ // The following methods are monitor locked by mLock.
+ //
+ // For methods taking a streamID:
+ // if the streamID matches the Stream's mStreamID, then method proceeds
+ // else the command is ignored with no effect.
+
+ // returns true if the stream needs to be explicitly stopped.
+ bool requestStop(int32_t streamID);
+ void stop(); // explicit stop(), typically called from the worker thread.
+ void clearAudioTrack();
+ void pause(int32_t streamID);
+ void autoPause(); // see the Java SoundPool.autoPause documentation for details.
+ void resume(int32_t streamID);
+ void autoResume();
+ void mute(bool muting);
+ void dump() const;
+
+ // returns the pair stream if successful, nullptr otherwise
+ Stream* playPairStream();
+
+ // These parameters are explicitly checked in the SoundPool class
+ // so never deviate from the Java API specified values.
+ void setVolume(int32_t streamID, float leftVolume, float rightVolume);
+ void setRate(int32_t streamID, float rate);
+ void setPriority(int32_t streamID, int priority);
+ void setLoop(int32_t streamID, int loop);
+ void setPlay(int32_t streamID, const std::shared_ptr<Sound> &sound, int32_t soundID,
+ float leftVolume, float rightVolume, int32_t priority, int32_t loop, float rate);
+ void setStopTimeNs(int64_t stopTimeNs); // systemTime() clock monotonic.
+
+ // The following getters are not locked and have weak consistency.
+ // These are considered advisory only - being stale is of nuisance.
+ int32_t getPriority() const { return mPriority; }
+ int32_t getPairPriority() const { return getPairStream()->getPriority(); }
+ int64_t getStopTimeNs() const { return mStopTimeNs; }
+
+ int32_t getStreamID() const { return mStreamID; } // Can change with setPlay()
+ int32_t getSoundID() const { return mSoundID; } // Can change with play_l()
+ bool hasSound() const { return mSound.get() != nullptr; }
+
+ Stream* getPairStream() const; // this never changes. See top of header.
+
+private:
+ void play_l(const std::shared_ptr<Sound>& sound, int streamID,
+ float leftVolume, float rightVolume, int priority, int loop, float rate,
+ sp<AudioTrack> releaseTracks[2]);
+ void stop_l();
+ void setVolume_l(float leftVolume, float rightVolume);
+
+ // For use with AudioTrack callback.
+ static void staticCallback(int event, void* user, void* info);
+ void callback(int event, void* info, int toggle, int tries);
+
+ // StreamManager should be set on construction and not changed.
+ // release mLock before calling into StreamManager
+ StreamManager* mStreamManager = nullptr;
+
+ mutable std::mutex mLock;
+ std::atomic_int32_t mStreamID = 0; // Note: valid streamIDs are always positive.
+ int mState = IDLE;
+ std::shared_ptr<Sound> mSound; // Non-null if playing.
+ int32_t mSoundID = 0; // The sound ID associated with the AudioTrack.
+ float mLeftVolume = 0.f;
+ float mRightVolume = 0.f;
+ int32_t mPriority = INT32_MIN;
+ int32_t mLoop = 0;
+ float mRate = 0.f;
+ bool mAutoPaused = false;
+ bool mMuted = false;
+
+ sp<AudioTrack> mAudioTrack;
+ int mToggle = 0;
+ int64_t mStopTimeNs = 0; // if nonzero, time to wait for stop.
+};
+
+} // namespace android::soundpool
diff --git a/media/jni/soundpool/StreamManager.cpp b/media/jni/soundpool/StreamManager.cpp
new file mode 100644
index 0000000..8928c47
--- /dev/null
+++ b/media/jni/soundpool/StreamManager.cpp
@@ -0,0 +1,407 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SoundPool::StreamManager"
+#include <utils/Log.h>
+
+#include "StreamManager.h"
+
+#include <audio_utils/clock.h>
+#include <audio_utils/roundup.h>
+
+namespace android::soundpool {
+
+// kMaxStreams is number that should be less than the current AudioTrack max per UID of 40.
+// It is the maximum number of AudioTrack resources allowed in the SoundPool.
+// We suggest a value at least 4 or greater to allow CTS tests to pass.
+static constexpr int32_t kMaxStreams = 32;
+
+// kStealActiveStream_OldestFirst = false historically (Q and earlier)
+// Changing to true could break app expectations but could change behavior beneficially.
+// In R, we change this to true, as it is the correct way per SoundPool documentation.
+static constexpr bool kStealActiveStream_OldestFirst = true;
+
+// kPlayOnCallingThread = true prior to R.
+// Changing to false means calls to play() are almost instantaneous instead of taking around
+// ~10ms to launch the AudioTrack. It is perhaps 100x faster.
+static constexpr bool kPlayOnCallingThread = false;
+
+// Amount of time for a StreamManager thread to wait before closing.
+static constexpr int64_t kWaitTimeBeforeCloseNs = 9 * NANOS_PER_SECOND;
+
+////////////
+
+StreamMap::StreamMap(int32_t streams) {
+ ALOGV("%s(%d)", __func__, streams);
+ if (streams > kMaxStreams) {
+ ALOGW("%s: requested %d streams, clamping to %d", __func__, streams, kMaxStreams);
+ streams = kMaxStreams;
+ } else if (streams < 1) {
+ ALOGW("%s: requested %d streams, clamping to 1", __func__, streams);
+ streams = 1;
+ }
+ mStreamPoolSize = streams * 2;
+ mStreamPool.reset(new Stream[mStreamPoolSize]);
+ // we use a perfect hash table with 2x size to map StreamIDs to Stream pointers.
+ mPerfectHash = std::make_unique<PerfectHash<int32_t, Stream *>>(roundup(mStreamPoolSize * 2));
+}
+
+Stream* StreamMap::findStream(int32_t streamID) const
+{
+ Stream *stream = lookupStreamFromId(streamID);
+ return stream != nullptr && stream->getStreamID() == streamID ? stream : nullptr;
+}
+
+size_t StreamMap::streamPosition(const Stream* stream) const
+{
+ ptrdiff_t index = stream - mStreamPool.get();
+ LOG_ALWAYS_FATAL_IF(index < 0 || index >= mStreamPoolSize,
+ "%s: stream position out of range: %td", __func__, index);
+ return (size_t)index;
+}
+
+Stream* StreamMap::lookupStreamFromId(int32_t streamID) const
+{
+ return streamID > 0 ? mPerfectHash->getValue(streamID).load() : nullptr;
+}
+
+int32_t StreamMap::getNextIdForStream(Stream* stream) const {
+ // even though it is const, it mutates the internal hash table.
+ const int32_t id = mPerfectHash->generateKey(
+ stream,
+ [] (Stream *stream) {
+ return stream == nullptr ? 0 : stream->getStreamID();
+ }, /* getKforV() */
+ stream->getStreamID() /* oldID */);
+ return id;
+}
+
+////////////
+
+StreamManager::StreamManager(
+ int32_t streams, size_t threads, const audio_attributes_t* attributes)
+ : StreamMap(streams)
+ , mAttributes(*attributes)
+{
+ ALOGV("%s(%d, %zu, ...)", __func__, streams, threads);
+ forEach([this](Stream *stream) {
+ stream->setStreamManager(this);
+ if ((streamPosition(stream) & 1) == 0) { // put the first stream of pair as available.
+ mAvailableStreams.insert(stream);
+ }
+ });
+
+ mThreadPool = std::make_unique<ThreadPool>(
+ std::min(threads, (size_t)std::thread::hardware_concurrency()),
+ "SoundPool_");
+}
+
+StreamManager::~StreamManager()
+{
+ ALOGV("%s", __func__);
+ {
+ std::unique_lock lock(mStreamManagerLock);
+ mQuit = true;
+ mStreamManagerCondition.notify_all();
+ }
+ mThreadPool->quit();
+
+ // call stop on the stream pool
+ forEach([](Stream *stream) { stream->stop(); });
+
+ // This invokes the destructor on the AudioTracks -
+ // we do it here to ensure that AudioTrack callbacks will not occur
+ // afterwards.
+ forEach([](Stream *stream) { stream->clearAudioTrack(); });
+}
+
+
+int32_t StreamManager::queueForPlay(const std::shared_ptr<Sound> &sound,
+ int32_t soundID, float leftVolume, float rightVolume,
+ int32_t priority, int32_t loop, float rate)
+{
+ ALOGV("%s(sound=%p, soundID=%d, leftVolume=%f, rightVolume=%f, priority=%d, loop=%d, rate=%f)",
+ __func__, sound.get(), soundID, leftVolume, rightVolume, priority, loop, rate);
+ bool launchThread = false;
+ int32_t streamID = 0;
+
+ { // for lock
+ std::unique_lock lock(mStreamManagerLock);
+ Stream *newStream = nullptr;
+ bool fromAvailableQueue = false;
+ ALOGV("%s: mStreamManagerLock lock acquired", __func__);
+
+ sanityCheckQueue_l();
+ // find an available stream, prefer one that has matching sound id.
+ if (mAvailableStreams.size() > 0) {
+ newStream = *mAvailableStreams.begin();
+ for (auto stream : mAvailableStreams) {
+ if (stream->getSoundID() == soundID) {
+ newStream = stream;
+ break;
+ }
+ }
+ if (newStream != nullptr) {
+ newStream->setStopTimeNs(systemTime());
+ }
+ fromAvailableQueue = true;
+ }
+
+ // also look in the streams restarting (if the paired stream doesn't have a pending play)
+ if (newStream == nullptr || newStream->getSoundID() != soundID) {
+ for (auto [unused , stream] : mRestartStreams) {
+ if (!stream->getPairStream()->hasSound()) {
+ if (stream->getSoundID() == soundID) {
+ newStream = stream;
+ break;
+ } else if (newStream == nullptr) {
+ newStream = stream;
+ }
+ }
+ }
+ }
+
+ // no available streams, look for one to steal from the active list
+ if (newStream == nullptr) {
+ for (auto stream : mActiveStreams) {
+ if (stream->getPriority() <= priority) {
+ if (newStream == nullptr
+ || newStream->getPriority() > stream->getPriority()) {
+ newStream = stream;
+ }
+ }
+ }
+ if (newStream != nullptr) { // we need to mute as it is still playing.
+ (void)newStream->requestStop(newStream->getStreamID());
+ }
+ }
+
+ // none found, look for a stream that is restarting, evict one.
+ if (newStream == nullptr) {
+ for (auto [unused, stream] : mRestartStreams) {
+ if (stream->getPairPriority() <= priority) {
+ newStream = stream;
+ break;
+ }
+ }
+ }
+
+ // DO NOT LOOK into mProcessingStreams as those are held by the StreamManager threads.
+
+ if (newStream == nullptr) {
+ ALOGD("%s: unable to find stream, returning 0", __func__);
+ return 0; // unable to find available stream
+ }
+
+ Stream *pairStream = newStream->getPairStream();
+ streamID = getNextIdForStream(pairStream);
+ pairStream->setPlay(
+ streamID, sound, soundID, leftVolume, rightVolume, priority, loop, rate);
+ if (fromAvailableQueue && kPlayOnCallingThread) {
+ removeFromQueues_l(newStream);
+ mProcessingStreams.emplace(newStream);
+ lock.unlock();
+ if (Stream* nextStream = newStream->playPairStream()) {
+ lock.lock();
+ ALOGV("%s: starting streamID:%d", __func__, nextStream->getStreamID());
+ addToActiveQueue_l(nextStream);
+ } else {
+ lock.lock();
+ mAvailableStreams.insert(newStream);
+ streamID = 0;
+ }
+ mProcessingStreams.erase(newStream);
+ } else {
+ launchThread = moveToRestartQueue_l(newStream) && needMoreThreads_l();
+ }
+ sanityCheckQueue_l();
+ ALOGV("%s: mStreamManagerLock released", __func__);
+ } // lock
+
+ if (launchThread) {
+ const int32_t id __unused = mThreadPool->launch([this](int32_t id) { run(id); });
+ ALOGV_IF(id != 0, "%s: launched thread %d", __func__, id);
+ }
+ ALOGV("%s: returning %d", __func__, streamID);
+ return streamID;
+}
+
+void StreamManager::moveToRestartQueue(
+ Stream* stream, int32_t activeStreamIDToMatch)
+{
+ ALOGV("%s(stream(ID)=%d, activeStreamIDToMatch=%d)",
+ __func__, stream->getStreamID(), activeStreamIDToMatch);
+ bool restart;
+ {
+ std::lock_guard lock(mStreamManagerLock);
+ sanityCheckQueue_l();
+ if (mProcessingStreams.count(stream) > 0 ||
+ mProcessingStreams.count(stream->getPairStream()) > 0) {
+ ALOGD("%s: attempting to restart processing stream(%d)",
+ __func__, stream->getStreamID());
+ restart = false;
+ } else {
+ moveToRestartQueue_l(stream, activeStreamIDToMatch);
+ restart = needMoreThreads_l();
+ }
+ sanityCheckQueue_l();
+ }
+ if (restart) {
+ const int32_t id __unused = mThreadPool->launch([this](int32_t id) { run(id); });
+ ALOGV_IF(id != 0, "%s: launched thread %d", __func__, id);
+ }
+}
+
+bool StreamManager::moveToRestartQueue_l(
+ Stream* stream, int32_t activeStreamIDToMatch)
+{
+ ALOGV("%s(stream(ID)=%d, activeStreamIDToMatch=%d)",
+ __func__, stream->getStreamID(), activeStreamIDToMatch);
+ if (activeStreamIDToMatch > 0 && stream->getStreamID() != activeStreamIDToMatch) {
+ return false;
+ }
+ const ssize_t found = removeFromQueues_l(stream, activeStreamIDToMatch);
+ if (found < 0) return false;
+
+ LOG_ALWAYS_FATAL_IF(found > 1, "stream on %zd > 1 stream lists", found);
+
+ addToRestartQueue_l(stream);
+ mStreamManagerCondition.notify_one();
+ return true;
+}
+
+ssize_t StreamManager::removeFromQueues_l(
+ Stream* stream, int32_t activeStreamIDToMatch) {
+ size_t found = 0;
+ for (auto it = mActiveStreams.begin(); it != mActiveStreams.end(); ++it) {
+ if (*it == stream) {
+ mActiveStreams.erase(it); // we erase the iterator and break (otherwise it not safe).
+ ++found;
+ break;
+ }
+ }
+ // activeStreamIDToMatch is nonzero indicates we proceed only if found.
+ if (found == 0 && activeStreamIDToMatch > 0) {
+ return -1; // special code: not present on active streams, ignore restart request
+ }
+
+ for (auto it = mRestartStreams.begin(); it != mRestartStreams.end(); ++it) {
+ if (it->second == stream) {
+ mRestartStreams.erase(it);
+ ++found;
+ break;
+ }
+ }
+ found += mAvailableStreams.erase(stream);
+
+ // streams on mProcessingStreams are undergoing processing by the StreamManager thread
+ // and do not participate in normal stream migration.
+ return found;
+}
+
+void StreamManager::addToRestartQueue_l(Stream *stream) {
+ mRestartStreams.emplace(stream->getStopTimeNs(), stream);
+}
+
+void StreamManager::addToActiveQueue_l(Stream *stream) {
+ if (kStealActiveStream_OldestFirst) {
+ mActiveStreams.push_back(stream); // oldest to newest
+ } else {
+ mActiveStreams.push_front(stream); // newest to oldest
+ }
+}
+
+void StreamManager::run(int32_t id)
+{
+ ALOGV("%s(%d) entering", __func__, id);
+ int64_t waitTimeNs = kWaitTimeBeforeCloseNs;
+ std::unique_lock lock(mStreamManagerLock);
+ while (!mQuit) {
+ mStreamManagerCondition.wait_for(
+ lock, std::chrono::duration<int64_t, std::nano>(waitTimeNs));
+ ALOGV("%s(%d) awake", __func__, id);
+
+ sanityCheckQueue_l();
+
+ if (mQuit || (mRestartStreams.empty() && waitTimeNs == kWaitTimeBeforeCloseNs)) {
+ break; // end the thread
+ }
+
+ waitTimeNs = kWaitTimeBeforeCloseNs;
+ while (!mQuit && !mRestartStreams.empty()) {
+ const nsecs_t nowNs = systemTime();
+ auto it = mRestartStreams.begin();
+ Stream* const stream = it->second;
+ const int64_t diffNs = stream->getStopTimeNs() - nowNs;
+ if (diffNs > 0) {
+ waitTimeNs = std::min(waitTimeNs, diffNs);
+ break;
+ }
+ mRestartStreams.erase(it);
+ mProcessingStreams.emplace(stream);
+ lock.unlock();
+ stream->stop();
+ ALOGV("%s(%d) stopping streamID:%d", __func__, id, stream->getStreamID());
+ if (Stream* nextStream = stream->playPairStream()) {
+ ALOGV("%s(%d) starting streamID:%d", __func__, id, nextStream->getStreamID());
+ lock.lock();
+ if (nextStream->getStopTimeNs() > 0) {
+ // the next stream was stopped before we can move it to the active queue.
+ ALOGV("%s(%d) stopping started streamID:%d",
+ __func__, id, nextStream->getStreamID());
+ moveToRestartQueue_l(nextStream);
+ } else {
+ addToActiveQueue_l(nextStream);
+ }
+ } else {
+ lock.lock();
+ mAvailableStreams.insert(stream);
+ }
+ mProcessingStreams.erase(stream);
+ sanityCheckQueue_l();
+ }
+ }
+ ALOGV("%s(%d) exiting", __func__, id);
+}
+
+void StreamManager::dump() const
+{
+ forEach([](const Stream *stream) { stream->dump(); });
+}
+
+void StreamManager::sanityCheckQueue_l() const
+{
+ // We want to preserve the invariant that each stream pair is exactly on one of the queues.
+ const size_t availableStreams = mAvailableStreams.size();
+ const size_t restartStreams = mRestartStreams.size();
+ const size_t activeStreams = mActiveStreams.size();
+ const size_t processingStreams = mProcessingStreams.size();
+ const size_t managedStreams = availableStreams + restartStreams + activeStreams
+ + processingStreams;
+ const size_t totalStreams = getStreamMapSize() >> 1;
+ LOG_ALWAYS_FATAL_IF(managedStreams != totalStreams,
+ "%s: mAvailableStreams:%zu + mRestartStreams:%zu + "
+ "mActiveStreams:%zu + mProcessingStreams:%zu = %zu != total streams %zu",
+ __func__, availableStreams, restartStreams, activeStreams, processingStreams,
+ managedStreams, totalStreams);
+ ALOGV("%s: mAvailableStreams:%zu + mRestartStreams:%zu + "
+ "mActiveStreams:%zu + mProcessingStreams:%zu = %zu (total streams: %zu)",
+ __func__, availableStreams, restartStreams, activeStreams, processingStreams,
+ managedStreams, totalStreams);
+}
+
+} // namespace android::soundpool
diff --git a/media/jni/soundpool/StreamManager.h b/media/jni/soundpool/StreamManager.h
new file mode 100644
index 0000000..8c98ac9
--- /dev/null
+++ b/media/jni/soundpool/StreamManager.h
@@ -0,0 +1,467 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#pragma once
+
+#include "Stream.h"
+
+#include <condition_variable>
+#include <future>
+#include <list>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <unordered_set>
+#include <vector>
+
+#include <utils/AndroidThreads.h>
+
+namespace android::soundpool {
+
+// TODO: Move helper classes to a utility file, with separate test.
+
+/**
+ * JavaThread is used like std::thread but for threads that may call the JVM.
+ *
+ * std::thread does not easily attach to the JVM. We need JVM capable threads
+ * from createThreadEtc() since android binder call optimization may attempt to
+ * call back into Java if the SoundPool runs in system server.
+ *
+ *
+ * No locking is required - the member variables are inherently thread-safe.
+ */
+class JavaThread {
+public:
+ JavaThread(std::function<void()> f, const char *name)
+ : mF{std::move(f)} {
+ createThreadEtc(staticFunction, this, name);
+ }
+
+ JavaThread(JavaThread &&) = delete; // uses "this" ptr, not moveable.
+
+ void join() const {
+ mFuture.wait();
+ }
+
+ bool isClosed() const {
+ return mIsClosed;
+ }
+
+private:
+ static int staticFunction(void *data) {
+ JavaThread *jt = static_cast<JavaThread *>(data);
+ jt->mF();
+ jt->mIsClosed = true;
+ jt->mPromise.set_value();
+ return 0;
+ }
+
+ // No locking is provided as these variables are initialized in the constructor
+ // and the members referenced are thread-safe objects.
+ // (mFuture.wait() can block multiple threads.)
+ // Note the order of member variables is reversed for destructor.
+ const std::function<void()> mF;
+ // Used in join() to block until the thread completes.
+ // See https://en.cppreference.com/w/cpp/thread/promise for the void specialization of
+ // promise.
+ std::promise<void> mPromise;
+ std::future<void> mFuture{mPromise.get_future()};
+ std::atomic_bool mIsClosed = false;
+};
+
+/**
+ * The ThreadPool manages thread lifetimes of SoundPool worker threads.
+ *
+ * TODO: the (eventual) goal of ThreadPool is to transparently and cooperatively
+ * maximize CPU utilization while avoiding starvation of other applications.
+ * Some possibilities:
+ *
+ * We should create worker threads when we have SoundPool work and the system is idle.
+ * CPU cycles are "use-it-or-lose-it" when the system is idle.
+ *
+ * We should adjust the priority of worker threads so that the second (and subsequent) worker
+ * threads have lower priority (should we try to promote priority also?).
+ *
+ * We should throttle the spawning of new worker threads, spacing over time, to avoid
+ * creating too many new threads all at once, on initialization.
+ */
+class ThreadPool {
+public:
+ ThreadPool(size_t maxThreadCount, std::string name)
+ : mMaxThreadCount(maxThreadCount)
+ , mName{std::move(name)} { }
+
+ ~ThreadPool() { quit(); }
+
+ size_t getActiveThreadCount() const { return mActiveThreadCount; }
+ size_t getMaxThreadCount() const { return mMaxThreadCount; }
+
+ void quit() {
+ std::list<std::unique_ptr<JavaThread>> threads;
+ {
+ std::lock_guard lock(mThreadLock);
+ if (mQuit) return; // already joined.
+ mQuit = true;
+ threads = std::move(mThreads);
+ mThreads.clear();
+ }
+ // mQuit set under lock, no more threads will be created.
+ for (auto &thread : threads) {
+ thread->join();
+ thread.reset();
+ }
+ LOG_ALWAYS_FATAL_IF(mActiveThreadCount != 0,
+ "Invalid Active Threads: %zu", (size_t)mActiveThreadCount);
+ }
+
+ // returns a non-zero id if successful, the id is to help logging messages.
+ int32_t launch(std::function<void(int32_t /* id */)> f) {
+ std::list<std::unique_ptr<JavaThread>> threadsToRelease; // release outside of lock.
+ std::lock_guard lock(mThreadLock);
+ if (mQuit) return 0; // ignore if we have quit
+
+ // clean up threads.
+ for (auto it = mThreads.begin(); it != mThreads.end(); ) {
+ if ((*it)->isClosed()) {
+ threadsToRelease.emplace_back(std::move(*it));
+ it = mThreads.erase(it);
+ } else {
+ ++it;
+ }
+ }
+
+ const size_t threadCount = mThreads.size();
+ if (threadCount < mMaxThreadCount) {
+ // if the id wraps, we don't care about collisions. it's just for logging.
+ mNextThreadId = mNextThreadId == INT32_MAX ? 1 : ++mNextThreadId;
+ const int32_t id = mNextThreadId;
+ mThreads.emplace_back(std::make_unique<JavaThread>(
+ [this, id, mf = std::move(f)] { mf(id); --mActiveThreadCount; },
+ (mName + std::to_string(id)).c_str()));
+ ++mActiveThreadCount;
+ return id;
+ }
+ return 0;
+ }
+
+ // TODO: launch only if load average is low.
+ // This gets the load average
+ // See also std::thread::hardware_concurrency() for the concurrent capability.
+ static double getLoadAvg() {
+ double loadAvg[1];
+ if (getloadavg(loadAvg, std::size(loadAvg)) > 0) {
+ return loadAvg[0];
+ }
+ return -1.;
+ }
+
+private:
+ const size_t mMaxThreadCount;
+ const std::string mName;
+
+ std::atomic_size_t mActiveThreadCount = 0;
+
+ std::mutex mThreadLock;
+ bool mQuit = false; // GUARDED_BY(mThreadLock)
+ int32_t mNextThreadId = 0; // GUARDED_BY(mThreadLock)
+ std::list<std::unique_ptr<JavaThread>> mThreads; // GUARDED_BY(mThreadLock)
+};
+
+/**
+ * A Perfect HashTable for IDs (key) to pointers (value).
+ *
+ * There are no collisions. Why? because we generate the IDs for you to look up :-).
+ *
+ * The goal of this hash table is to map an integer ID handle > 0 to a pointer.
+ * We give these IDs in monotonic order (though we may skip if it were to cause a collision).
+ *
+ * The size of the hashtable must be large enough to accommodate the max number of keys.
+ * We suggest 2x.
+ *
+ * Readers are lockless
+ * Single writer could be lockless, but we allow multiple writers through an internal lock.
+ *
+ * For the Key type K, valid keys generated are > 0 (signed or unsigned)
+ * For the Value type V, values are pointers - nullptr means empty.
+ */
+template <typename K, typename V>
+class PerfectHash {
+public:
+ PerfectHash(size_t hashCapacity)
+ : mHashCapacity(hashCapacity)
+ , mK2V{new std::atomic<V>[hashCapacity]()} {
+ }
+
+ // Generate a key for a value V.
+ // There is a testing function getKforV() which checks what the value reports as its key.
+ //
+ // Calls back into getKforV under lock.
+ //
+ // We expect that the hashCapacity is 2x the number of stored keys in order
+ // to have one or two tries to find an empty slot
+ K generateKey(V value, std::function<K(V)> getKforV, K oldKey = 0) {
+ std::lock_guard lock(mHashLock);
+ // try to remove the old key.
+ if (oldKey > 0) { // key valid
+ const V v = getValue(oldKey);
+ if (v != nullptr) { // value still valid
+ const K atPosition = getKforV(v);
+ if (atPosition < 0 || // invalid value
+ atPosition == oldKey || // value's key still valid and matches old key
+ ((atPosition ^ oldKey) & (mHashCapacity - 1)) != 0) { // stale key entry
+ getValue(oldKey) = nullptr; // invalidate
+ }
+ } // else if value is invalid, no need to invalidate.
+ }
+ // check if we are invalidating only.
+ if (value == nullptr) return 0;
+ // now insert the new value and return the key.
+ size_t tries = 0;
+ for (; tries < mHashCapacity; ++tries) {
+ mNextKey = mNextKey == std::numeric_limits<K>::max() ? 1 : mNextKey + 1;
+ const V v = getValue(mNextKey);
+ //ALOGD("tries: %zu, key:%d value:%p", tries, (int)mNextKey, v);
+ if (v == nullptr) break; // empty
+ const K atPosition = getKforV(v);
+ //ALOGD("tries: %zu key atPosition:%d", tries, (int)atPosition);
+ if (atPosition < 0 || // invalid value
+ ((atPosition ^ mNextKey) & (mHashCapacity - 1)) != 0) { // stale key entry
+ break;
+ }
+ }
+ LOG_ALWAYS_FATAL_IF(tries == mHashCapacity, "hash table overflow!");
+ //ALOGD("%s: found after %zu tries", __func__, tries);
+ getValue(mNextKey) = value;
+ return mNextKey;
+ }
+
+ std::atomic<V> &getValue(K key) { return mK2V[key & (mHashCapacity - 1)]; }
+ const std::atomic_int32_t &getValue(K key) const { return mK2V[key & (mHashCapacity - 1)]; }
+
+private:
+ mutable std::mutex mHashLock;
+ const size_t mHashCapacity; // size of mK2V no lock needed.
+ std::unique_ptr<std::atomic<V>[]> mK2V; // no lock needed for read access.
+ K mNextKey{}; // GUARDED_BY(mHashLock)
+};
+
+/**
+ * StreamMap contains the all the valid streams available to SoundPool.
+ *
+ * There is no Lock required for this class because the streams are
+ * allocated in the constructor, the lookup is lockless, and the Streams
+ * returned are locked internally.
+ *
+ * The lookup uses a perfect hash.
+ * It is possible to use a lockless hash table or to use a stripe-locked concurrent
+ * hashmap for essentially lock-free lookup.
+ *
+ * This follows Map-Reduce parallelism model.
+ * https://en.wikipedia.org/wiki/MapReduce
+ *
+ * Conceivably the forEach could be parallelized using std::for_each with a
+ * std::execution::par policy.
+ *
+ * https://en.cppreference.com/w/cpp/algorithm/for_each
+ */
+class StreamMap {
+public:
+ explicit StreamMap(int32_t streams);
+
+ // Returns the stream associated with streamID or nullptr if not found.
+ // This need not be locked.
+ // The stream ID will never migrate to another Stream, but it may change
+ // underneath you. The Stream operations that take a streamID will confirm
+ // that the streamID matches under the Stream lock before executing otherwise
+ // it ignores the command as stale.
+ Stream* findStream(int32_t streamID) const;
+
+ // Iterates through the stream pool applying the function f.
+ // Since this enumerates over every single stream, it is unlocked.
+ //
+ // See related: https://en.cppreference.com/w/cpp/algorithm/for_each
+ void forEach(std::function<void(const Stream *)>f) const {
+ for (size_t i = 0; i < mStreamPoolSize; ++i) {
+ f(&mStreamPool[i]);
+ }
+ }
+
+ void forEach(std::function<void(Stream *)>f) {
+ for (size_t i = 0; i < mStreamPoolSize; ++i) {
+ f(&mStreamPool[i]);
+ }
+ }
+
+ // Returns the pair stream for a given Stream.
+ // This need not be locked as it is a property of the pointer address.
+ Stream* getPairStream(const Stream* stream) const {
+ const size_t index = streamPosition(stream);
+ return &mStreamPool[index ^ 1];
+ }
+
+ // find the position of the stream in mStreamPool array.
+ size_t streamPosition(const Stream* stream) const; // no lock needed
+
+ size_t getStreamMapSize() const {
+ return mStreamPoolSize;
+ }
+
+ // find the next valid ID for a stream and store in hash table.
+ int32_t getNextIdForStream(Stream* stream) const;
+
+private:
+
+ // use the hash table to attempt to find the stream.
+ // nullptr is returned if the lookup fails.
+ Stream* lookupStreamFromId(int32_t streamID) const;
+
+ // The stream pool is initialized in the constructor, effectively const.
+ // no locking required for access.
+ //
+ // The constructor parameter "streams" results in streams pairs of streams.
+ // We have twice as many streams because we wish to return a streamID "handle"
+ // back to the app immediately, while we may be stopping the other stream in the
+ // pair to get its AudioTrack :-).
+ //
+ // Of the stream pair, only one of the streams may have an AudioTrack.
+ // The fixed association of a stream pair allows callbacks from the AudioTrack
+ // to be associated properly to either one or the other of the stream pair.
+ //
+ // TODO: The stream pair arrangement can be removed if we have better AudioTrack
+ // callback handling (being able to remove and change the callback after construction).
+ //
+ // Streams may be accessed anytime off of the stream pool
+ // as there is internal locking on each stream.
+ std::unique_ptr<Stream[]> mStreamPool; // no lock needed for access.
+ size_t mStreamPoolSize; // no lock needed for access.
+
+ // In order to find the Stream from a StreamID, we could do a linear lookup in mStreamPool.
+ // As an alternative, one could use stripe-locked or lock-free concurrent hashtables.
+ //
+ // When considering linear search vs hashmap, verify the typical use-case size.
+ // Linear search is faster than std::unordered_map (circa 2018) for less than 40 elements.
+ // [ Skarupke, M. (2018), "You Can Do Better than std::unordered_map: New and Recent
+ // Improvements to Hash Table Performance." C++Now 2018. cppnow.org, see
+ // https://www.youtube.com/watch?v=M2fKMP47slQ ]
+ //
+ // Here, we use a PerfectHash of Id to Stream *, since we can control the
+ // StreamID returned to the user. This allows O(1) read access to mStreamPool lock-free.
+ //
+ // We prefer that the next stream ID is monotonic for aesthetic reasons
+ // (if we didn't care about monotonicity, a simple method is to apply a generation count
+ // to each stream in the unused upper bits of its index in mStreamPool for the id).
+ //
+ std::unique_ptr<PerfectHash<int32_t, Stream *>> mPerfectHash;
+};
+
+/**
+ * StreamManager is used to manage the streams (accessed by StreamID from Java).
+ *
+ * Locking order (proceeds from application to component).
+ * SoundPool mApiLock (if needed) -> StreamManager mStreamManagerLock
+ * -> pair Stream mLock -> queued Stream mLock
+ */
+class StreamManager : public StreamMap {
+public:
+ // Note: the SoundPool pointer is only used for stream initialization.
+ // It is not stored in StreamManager.
+ StreamManager(int32_t streams, size_t threads, const audio_attributes_t* attributes);
+ ~StreamManager();
+
+ // Returns positive streamID on success, 0 on failure. This is locked.
+ int32_t queueForPlay(const std::shared_ptr<Sound> &sound,
+ int32_t soundID, float leftVolume, float rightVolume,
+ int32_t priority, int32_t loop, float rate);
+
+ ///////////////////////////////////////////////////////////////////////
+ // Called from soundpool::Stream
+
+ const audio_attributes_t* getAttributes() const { return &mAttributes; }
+
+ // Moves the stream to the restart queue (called upon BUFFER_END of the static track)
+ // this is locked internally.
+ // If activeStreamIDToMatch is nonzero, it will only move to the restart queue
+ // if the streamIDToMatch is found on the active queue.
+ void moveToRestartQueue(Stream* stream, int32_t activeStreamIDToMatch = 0);
+
+private:
+
+ void run(int32_t id); // worker thread, takes lock internally.
+ void dump() const; // no lock needed
+
+ // returns true if more worker threads are needed.
+ bool needMoreThreads_l() {
+ return mRestartStreams.size() > 0 &&
+ (mThreadPool->getActiveThreadCount() == 0
+ || std::distance(mRestartStreams.begin(),
+ mRestartStreams.upper_bound(systemTime()))
+ > (ptrdiff_t)mThreadPool->getActiveThreadCount());
+ }
+
+ // returns true if the stream was added.
+ bool moveToRestartQueue_l(Stream* stream, int32_t activeStreamIDToMatch = 0);
+ // returns number of queues the stream was removed from (should be 0 or 1);
+ // a special code of -1 is returned if activeStreamIDToMatch is > 0 and
+ // the stream wasn't found on the active queue.
+ ssize_t removeFromQueues_l(Stream* stream, int32_t activeStreamIDToMatch = 0);
+ void addToRestartQueue_l(Stream *stream);
+ void addToActiveQueue_l(Stream *stream);
+ void sanityCheckQueue_l() const;
+
+ const audio_attributes_t mAttributes;
+ std::unique_ptr<ThreadPool> mThreadPool; // locked internally
+
+ // mStreamManagerLock is used to lock access for transitions between the
+ // 4 stream queues by the Manager Thread or by the user initiated play().
+ // A stream pair has exactly one stream on exactly one of the queues.
+ std::mutex mStreamManagerLock;
+ std::condition_variable mStreamManagerCondition;
+
+ bool mQuit = false; // GUARDED_BY(mStreamManagerLock)
+
+ // There are constructor arg "streams" pairs of streams, only one of each
+ // pair on the 4 stream queues below. The other stream in the pair serves as
+ // placeholder to accumulate user changes, pending actual availability of the
+ // AudioTrack, as it may be in use, requiring stop-then-restart.
+ //
+ // The 4 queues are implemented in the appropriate STL container based on perceived
+ // optimality.
+
+ // 1) mRestartStreams: Streams awaiting stop.
+ // The paired stream may be active (but with no AudioTrack), and will be restarted
+ // with an active AudioTrack when the current stream is stopped.
+ std::multimap<int64_t /* stopTimeNs */, Stream*>
+ mRestartStreams; // GUARDED_BY(mStreamManagerLock)
+
+ // 2) mActiveStreams: Streams that are active.
+ // The paired stream will be inactive.
+ // This is in order of specified by kStealActiveStream_OldestFirst
+ std::list<Stream*> mActiveStreams; // GUARDED_BY(mStreamManagerLock)
+
+ // 3) mAvailableStreams: Streams that are inactive.
+ // The paired stream will also be inactive.
+ // No particular order.
+ std::unordered_set<Stream*> mAvailableStreams; // GUARDED_BY(mStreamManagerLock)
+
+ // 4) mProcessingStreams: Streams that are being processed by the ManagerThreads
+ // When on this queue, the stream and its pair are not available for stealing.
+ // Each ManagerThread will have at most one stream on the mProcessingStreams queue.
+ // The paired stream may be active or restarting.
+ // No particular order.
+ std::unordered_set<Stream*> mProcessingStreams; // GUARDED_BY(mStreamManagerLock)
+};
+
+} // namespace android::soundpool
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 16073ae..52aaf4f 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -32,6 +32,7 @@
import android.content.res.Configuration;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.os.PowerManager;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.GestureDetector;
@@ -109,6 +110,8 @@
import com.android.systemui.statusbar.phone.BiometricUnlockController;
import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.DozeScrimController;
+import com.android.systemui.statusbar.phone.DozeServiceHost;
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.LightBarController;
@@ -301,6 +304,9 @@
ScrimController scrimController,
Lazy<LockscreenWallpaper> lockscreenWallpaperLazy,
Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
+ DozeServiceHost dozeServiceHost,
+ PowerManager powerManager,
+ DozeScrimController dozeScrimController,
/* Car Settings injected components. */
NavigationBarViewFactory navigationBarViewFactory) {
@@ -364,7 +370,10 @@
dozeParameters,
scrimController,
lockscreenWallpaperLazy,
- biometricUnlockControllerLazy);
+ biometricUnlockControllerLazy,
+ dozeServiceHost,
+ powerManager,
+ dozeScrimController);
mScrimController = scrimController;
mNavigationBarViewFactory = navigationBarViewFactory;
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
index 3014452..3b48259 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
@@ -18,6 +18,7 @@
import static android.content.DialogInterface.BUTTON_NEGATIVE;
import static android.content.DialogInterface.BUTTON_POSITIVE;
+import static android.os.UserManager.DISALLOW_ADD_USER;
import android.app.ActivityManager;
import android.app.AlertDialog;
@@ -145,7 +146,8 @@
userRecords.add(createStartGuestUserRecord());
// Add add user record if the foreground user can add users
- if (mCarUserManagerHelper.canForegroundUserAddUsers()) {
+ UserHandle fgUserHandle = UserHandle.of(ActivityManager.getCurrentUser());
+ if (!mUserManager.hasUserRestriction(DISALLOW_ADD_USER, fgUserHandle)) {
userRecords.add(createAddUserRecord());
}
@@ -285,7 +287,7 @@
}
private void handleAddUserClicked() {
- if (mCarUserManagerHelper.isUserLimitReached()) {
+ if (!mUserManager.canAddMoreUsers()) {
mAddUserView.setEnabled(true);
showMaxUserLimitReachedDialog();
} else {
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/NotificationPersonExtractorPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/NotificationPersonExtractorPlugin.java
new file mode 100644
index 0000000..802a8da
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/NotificationPersonExtractorPlugin.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 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 com.android.systemui.plugins;
+
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.graphics.drawable.Drawable;
+import android.service.notification.StatusBarNotification;
+
+import com.android.systemui.plugins.annotations.DependsOn;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/** Custom logic that can extract a PeopleHub "person" from a notification. */
+@ProvidesInterface(
+ action = NotificationPersonExtractorPlugin.ACTION,
+ version = NotificationPersonExtractorPlugin.VERSION)
+@DependsOn(target = NotificationPersonExtractorPlugin.PersonData.class)
+public interface NotificationPersonExtractorPlugin extends Plugin {
+
+ String ACTION = "com.android.systemui.action.PEOPLE_HUB_PERSON_EXTRACTOR";
+ int VERSION = 0;
+
+ /**
+ * Attempts to extract a person from a notification. Returns {@code null} if one is not found.
+ */
+ @Nullable PersonData extractPerson(StatusBarNotification sbn);
+
+ /**
+ * Attempts to extract a person id from a notification. Returns {@code null} if one is not
+ * found.
+ *
+ * This method can be overridden in order to provide a faster implementation.
+ */
+ @Nullable
+ default String extractPersonKey(StatusBarNotification sbn) {
+ return extractPerson(sbn).key;
+ }
+
+ /** A person to be surfaced in PeopleHub. */
+ @ProvidesInterface(version = PersonData.VERSION)
+ final class PersonData {
+
+ public static final int VERSION = 0;
+
+ public final String key;
+ public final CharSequence name;
+ public final Drawable avatar;
+ public final PendingIntent clickIntent;
+
+ public PersonData(String key, CharSequence name, Drawable avatar,
+ PendingIntent clickIntent) {
+ this.key = key;
+ this.name = name;
+ this.avatar = avatar;
+ this.clickIntent = clickIntent;
+ }
+ }
+}
diff --git a/packages/SystemUI/res/layout/people_strip.xml b/packages/SystemUI/res/layout/people_strip.xml
index b314db8..f0ac08b 100644
--- a/packages/SystemUI/res/layout/people_strip.xml
+++ b/packages/SystemUI/res/layout/people_strip.xml
@@ -33,10 +33,9 @@
<LinearLayout
android:id="@+id/people_list"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:paddingTop="8dp"
- android:paddingBottom="8dp"
+ android:layout_height="match_parent"
+ android:paddingTop="12dp"
+ android:paddingBottom="12dp"
android:gravity="center"
android:orientation="horizontal">
@@ -49,7 +48,7 @@
<LinearLayout
android:layout_width="70dp"
android:layout_height="match_parent"
- android:gravity="center"
+ android:gravity="center_horizontal"
android:orientation="vertical"
android:visibility="invisible">
@@ -87,7 +86,7 @@
<LinearLayout
android:layout_width="70dp"
android:layout_height="match_parent"
- android:gravity="center"
+ android:gravity="center_horizontal"
android:orientation="vertical"
android:visibility="invisible">
@@ -125,7 +124,7 @@
<LinearLayout
android:layout_width="70dp"
android:layout_height="match_parent"
- android:gravity="center"
+ android:gravity="center_horizontal"
android:orientation="vertical"
android:visibility="invisible">
@@ -163,7 +162,7 @@
<LinearLayout
android:layout_width="70dp"
android:layout_height="match_parent"
- android:gravity="center"
+ android:gravity="center_horizontal"
android:orientation="vertical"
android:visibility="invisible">
@@ -201,7 +200,7 @@
<LinearLayout
android:layout_width="70dp"
android:layout_height="match_parent"
- android:gravity="center"
+ android:gravity="center_horizontal"
android:orientation="vertical"
android:visibility="invisible">
diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
index ddbabee..30a60ab 100644
--- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java
+++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
@@ -16,6 +16,8 @@
package com.android.systemui;
+import static android.os.PowerManager.WAKE_REASON_UNKNOWN;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -28,21 +30,32 @@
import com.android.internal.util.LatencyTracker;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.statusbar.phone.BiometricUnlockController;
-import com.android.systemui.statusbar.phone.StatusBar;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
/**
* Class that only runs on debuggable builds that listens to broadcasts that simulate actions in the
* system that are used for testing the latency.
*/
+@Singleton
public class LatencyTester extends SystemUI {
- private static final String ACTION_FINGERPRINT_WAKE =
+ private static final String
+ ACTION_FINGERPRINT_WAKE =
"com.android.systemui.latency.ACTION_FINGERPRINT_WAKE";
- private static final String ACTION_TURN_ON_SCREEN =
+ private static final String
+ ACTION_TURN_ON_SCREEN =
"com.android.systemui.latency.ACTION_TURN_ON_SCREEN";
+ private final BiometricUnlockController mBiometricUnlockController;
+ private final PowerManager mPowerManager;
- public LatencyTester(Context context) {
+ @Inject
+ public LatencyTester(Context context, BiometricUnlockController biometricUnlockController,
+ PowerManager powerManager) {
super(context);
+ mBiometricUnlockController = biometricUnlockController;
+ mPowerManager = powerManager;
}
@Override
@@ -68,19 +81,17 @@
}
private void fakeTurnOnScreen() {
- PowerManager powerManager = mContext.getSystemService(PowerManager.class);
if (LatencyTracker.isEnabled(mContext)) {
LatencyTracker.getInstance(mContext).onActionStart(
LatencyTracker.ACTION_TURN_ON_SCREEN);
}
- powerManager.wakeUp(SystemClock.uptimeMillis(), "android.policy:LATENCY_TESTS");
+ mPowerManager.wakeUp(
+ SystemClock.uptimeMillis(), WAKE_REASON_UNKNOWN, "android.policy:LATENCY_TESTS");
}
private void fakeWakeAndUnlock() {
- BiometricUnlockController biometricUnlockController = getComponent(StatusBar.class)
- .getBiometricUnlockController();
- biometricUnlockController.onBiometricAcquired(BiometricSourceType.FINGERPRINT);
- biometricUnlockController.onBiometricAuthenticated(
+ mBiometricUnlockController.onBiometricAcquired(BiometricSourceType.FINGERPRINT);
+ mBiometricUnlockController.onBiometricAuthenticated(
KeyguardUpdateMonitor.getCurrentUser(), BiometricSourceType.FINGERPRINT);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
index 6674c12..9032c6f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
@@ -20,6 +20,7 @@
import com.android.systemui.appops.AppOpsController;
import com.android.systemui.appops.AppOpsControllerImpl;
import com.android.systemui.classifier.FalsingManagerProxy;
+import com.android.systemui.doze.DozeHost;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.FalsingManager;
@@ -33,6 +34,7 @@
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl;
+import com.android.systemui.statusbar.phone.DozeServiceHost;
import com.android.systemui.statusbar.phone.ManagedProfileController;
import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;
import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -241,5 +243,10 @@
/**
*/
@Binds
- public abstract FalsingManager provideFalsingmanager(FalsingManagerProxy falsingManagerImpl);
+ public abstract FalsingManager provideFalsingManager(FalsingManagerProxy falsingManagerImpl);
+
+ /**
+ */
+ @Binds
+ public abstract DozeHost provideDozeHost(DozeServiceHost dozeServiceHost);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index 49cd414..71c2e38 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -16,6 +16,7 @@
package com.android.systemui.dagger;
+import com.android.systemui.LatencyTester;
import com.android.systemui.SystemUI;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.pip.PipUI;
@@ -48,6 +49,12 @@
@ClassKey(KeyguardViewMediator.class)
public abstract SystemUI bindKeyguardViewMediator(KeyguardViewMediator sysui);
+ /** Inject into LatencyTests. */
+ @Binds
+ @IntoMap
+ @ClassKey(LatencyTester.class)
+ public abstract SystemUI bindLatencyTester(LatencyTester sysui);
+
/** Inject into PipUI. */
@Binds
@IntoMap
@@ -66,6 +73,7 @@
@ClassKey(Recents.class)
public abstract SystemUI bindRecents(Recents sysui);
+
/** Inject into VolumeUI. */
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 6e464f4..b4dc538 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -138,6 +138,14 @@
mNotificationEntryListeners.add(listener);
}
+ /**
+ * Removes a {@link NotificationEntryListener} previously registered via
+ * {@link #addNotificationEntryListener(NotificationEntryListener)}.
+ */
+ public void removeNotificationEntryListener(NotificationEntryListener listener) {
+ mNotificationEntryListeners.remove(listener);
+ }
+
/** Sets the {@link NotificationRemoveInterceptor}. */
public void setNotificationRemoveInterceptor(NotificationRemoveInterceptor interceptor) {
mRemoveInterceptor = interceptor;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
index 7e398bb..7d0ce5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
@@ -37,6 +37,7 @@
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
import com.android.systemui.statusbar.notification.logging.NotifEvent;
import com.android.systemui.statusbar.notification.logging.NotifLog;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -76,12 +77,16 @@
private final Ranking mTmpRanking = new Ranking();
private final boolean mUsePeopleFiltering;
private final NotifLog mNotifLog;
+ private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
@Inject
- public NotificationData(NotificationSectionsFeatureManager sectionsFeatureManager,
- NotifLog notifLog) {
+ public NotificationData(
+ NotificationSectionsFeatureManager sectionsFeatureManager,
+ NotifLog notifLog,
+ PeopleNotificationIdentifier peopleNotificationIdentifier) {
mUsePeopleFiltering = sectionsFeatureManager.isFilteringEnabled();
mNotifLog = notifLog;
+ mPeopleNotificationIdentifier = peopleNotificationIdentifier;
}
public void setHeadsUpManager(HeadsUpManager headsUpManager) {
@@ -460,8 +465,7 @@
}
private boolean isPeopleNotification(NotificationEntry e) {
- return e.getSbn().getNotification().getNotificationStyle()
- == Notification.MessagingStyle.class;
+ return mPeopleNotificationIdentifier.isPeopleNotification(e.getSbn());
}
public void dump(PrintWriter pw, String indent) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubModule.kt
index 8c067b7..4570989 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubModule.kt
@@ -24,14 +24,24 @@
@Binds
abstract fun peopleHubSectionFooterViewController(
- viewAdapter: PeopleHubSectionFooterViewAdapterImpl
+ impl: PeopleHubSectionFooterViewAdapterImpl
): PeopleHubSectionFooterViewAdapter
@Binds
- abstract fun peopleHubDataSource(s: PeopleHubDataSourceImpl): DataSource<PeopleHubModel>
+ abstract fun peopleHubDataSource(impl: PeopleHubDataSourceImpl): DataSource<PeopleHubModel>
@Binds
abstract fun peopleHubViewModelFactoryDataSource(
- dataSource: PeopleHubViewModelFactoryDataSourceImpl
+ impl: PeopleHubViewModelFactoryDataSourceImpl
): DataSource<PeopleHubViewModelFactory>
+
+ @Binds
+ abstract fun peopleNotificationIdentifier(
+ impl: PeopleNotificationIdentifierImpl
+ ): PeopleNotificationIdentifier
+
+ @Binds
+ abstract fun notificationPersonExtractor(
+ pluginImpl: NotificationPersonExtractorPluginBoundary
+ ): NotificationPersonExtractor
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
index c8a3c1a..fe257d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
@@ -17,11 +17,14 @@
package com.android.systemui.statusbar.notification.people
import android.app.Notification
+import android.content.Context
import android.graphics.Canvas
import android.graphics.ColorFilter
import android.graphics.PixelFormat
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import android.service.notification.StatusBarNotification
import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
@@ -30,59 +33,112 @@
import com.android.internal.widget.MessagingGroup
import com.android.launcher3.icons.BaseIconFactory
import com.android.systemui.R
+import com.android.systemui.plugins.NotificationPersonExtractorPlugin
import com.android.systemui.statusbar.notification.NotificationEntryListener
import com.android.systemui.statusbar.notification.NotificationEntryManager
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.policy.ExtensionController
import java.util.ArrayDeque
import javax.inject.Inject
import javax.inject.Singleton
private const val MAX_STORED_INACTIVE_PEOPLE = 10
-@Singleton
-class PeopleHubDataSourceImpl @Inject constructor(
- notificationEntryManager: NotificationEntryManager,
- private val peopleHubManager: PeopleHubManager
-) : DataSource<PeopleHubModel> {
+interface NotificationPersonExtractor {
+ fun extractPerson(sbn: StatusBarNotification): PersonModel?
+ fun extractPersonKey(sbn: StatusBarNotification): String?
+}
- private var dataListener: DataListener<PeopleHubModel>? = null
+@Singleton
+class NotificationPersonExtractorPluginBoundary @Inject constructor(
+ extensionController: ExtensionController,
+ private val context: Context
+) : NotificationPersonExtractor {
+
+ private var plugin: NotificationPersonExtractorPlugin? = null
init {
- notificationEntryManager.addNotificationEntryListener(object : NotificationEntryListener {
- override fun onEntryInflated(entry: NotificationEntry, inflatedFlags: Int) =
- addVisibleEntry(entry)
+ plugin = extensionController
+ .newExtension(NotificationPersonExtractorPlugin::class.java)
+ .withPlugin(NotificationPersonExtractorPlugin::class.java)
+ .withCallback { extractor ->
+ plugin = extractor
+ }
+ .build()
+ .get()
+ }
- override fun onEntryReinflated(entry: NotificationEntry) = addVisibleEntry(entry)
+ override fun extractPerson(sbn: StatusBarNotification) =
+ plugin?.extractPerson(sbn)?.let { data ->
+ val badged = addBadgeToDrawable(data.avatar, context, sbn.packageName, sbn.user)
+ PersonModel(data.key, data.name, badged, data.clickIntent)
+ }
- override fun onPostEntryUpdated(entry: NotificationEntry) = addVisibleEntry(entry)
+ override fun extractPersonKey(sbn: StatusBarNotification) = plugin?.extractPersonKey(sbn)
+}
- override fun onEntryRemoved(
- entry: NotificationEntry,
- visibility: NotificationVisibility?,
- removedByUser: Boolean
- ) = removeVisibleEntry(entry)
- })
+@Singleton
+class PeopleHubDataSourceImpl @Inject constructor(
+ private val notificationEntryManager: NotificationEntryManager,
+ private val peopleHubManager: PeopleHubManager,
+ private val extractor: NotificationPersonExtractor
+) : DataSource<PeopleHubModel> {
+
+ private val dataListeners = mutableListOf<DataListener<PeopleHubModel>>()
+
+ private val notificationEntryListener = object : NotificationEntryListener {
+ override fun onEntryInflated(entry: NotificationEntry, inflatedFlags: Int) =
+ addVisibleEntry(entry)
+
+ override fun onEntryReinflated(entry: NotificationEntry) = addVisibleEntry(entry)
+
+ override fun onPostEntryUpdated(entry: NotificationEntry) = addVisibleEntry(entry)
+
+ override fun onEntryRemoved(
+ entry: NotificationEntry,
+ visibility: NotificationVisibility?,
+ removedByUser: Boolean
+ ) = removeVisibleEntry(entry)
}
private fun removeVisibleEntry(entry: NotificationEntry) {
- if (entry.extractPersonKey()?.let(peopleHubManager::removeActivePerson) == true) {
+ val key = extractor.extractPersonKey(entry.sbn) ?: entry.extractPersonKey()
+ if (key?.let(peopleHubManager::removeActivePerson) == true) {
updateUi()
}
}
private fun addVisibleEntry(entry: NotificationEntry) {
- if (entry.extractPerson()?.let(peopleHubManager::addActivePerson) == true) {
+ val personModel = extractor.extractPerson(entry.sbn) ?: entry.extractPerson()
+ if (personModel?.let(peopleHubManager::addActivePerson) == true) {
updateUi()
}
}
- override fun setListener(listener: DataListener<PeopleHubModel>) {
- this.dataListener = listener
- updateUi()
+ override fun registerListener(listener: DataListener<PeopleHubModel>): Subscription {
+ val registerWithNotificationEntryManager = dataListeners.isEmpty()
+ dataListeners.add(listener)
+ if (registerWithNotificationEntryManager) {
+ notificationEntryManager.addNotificationEntryListener(notificationEntryListener)
+ } else {
+ listener.onDataChanged(peopleHubManager.getPeopleHubModel())
+ }
+ return object : Subscription {
+ override fun unsubscribe() {
+ dataListeners.remove(listener)
+ if (dataListeners.isEmpty()) {
+ notificationEntryManager
+ .removeNotificationEntryListener(notificationEntryListener)
+ }
+ }
+ }
}
private fun updateUi() {
- dataListener?.onDataChanged(peopleHubManager.getPeopleHubModel())
+ val model = peopleHubManager.getPeopleHubModel()
+ for (listener in dataListeners) {
+ listener.onDataChanged(model)
+ }
}
}
@@ -124,19 +180,26 @@
if (!isMessagingNotification()) {
return null
}
-
val clickIntent = sbn.notification.contentIntent
+ ?: return null
val extras = sbn.notification.extras
val name = extras.getString(Notification.EXTRA_CONVERSATION_TITLE)
?: extras.getString(Notification.EXTRA_TITLE)
?: return null
val drawable = extractAvatarFromRow(this) ?: return null
+ val badgedAvatar = addBadgeToDrawable(drawable, row.context, sbn.packageName, sbn.user)
+ return PersonModel(key, name, badgedAvatar, clickIntent)
+}
- val context = row.context
+private fun addBadgeToDrawable(
+ drawable: Drawable,
+ context: Context,
+ packageName: String,
+ user: UserHandle
+): Drawable {
val pm = context.packageManager
- val appInfo = pm.getApplicationInfoAsUser(sbn.packageName, 0, sbn.user)
-
- val badgedAvatar = object : Drawable() {
+ val appInfo = pm.getApplicationInfoAsUser(packageName, 0, user)
+ return object : Drawable() {
override fun draw(canvas: Canvas) {
val iconBounds = getBounds()
val factory = object : BaseIconFactory(
@@ -146,7 +209,7 @@
true) {}
val badge = factory.createBadgedIconBitmap(
appInfo.loadIcon(pm),
- sbn.user,
+ user,
true,
appInfo.isInstantApp,
null)
@@ -156,7 +219,7 @@
colorFilter = drawable.colorFilter
val badgeWidth = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
- 16f,
+ 15f,
context.resources.displayMetrics
).toInt()
setBounds(
@@ -181,8 +244,6 @@
@PixelFormat.Opacity
override fun getOpacity(): Int = PixelFormat.OPAQUE
}
-
- return PersonModel(key, name, badgedAvatar, clickIntent)
}
private fun extractAvatarFromRow(entry: NotificationEntry): Drawable? =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt
index 8d1253b..5c35408 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt
@@ -60,8 +60,9 @@
private val dataSource: DataSource<@JvmSuppressWildcards PeopleHubViewModelFactory>
) : PeopleHubSectionFooterViewAdapter {
- override fun bindView(viewBoundary: PeopleHubSectionFooterViewBoundary) =
- dataSource.setListener(PeopleHubDataListenerImpl(viewBoundary))
+ override fun bindView(viewBoundary: PeopleHubSectionFooterViewBoundary) {
+ dataSource.registerListener(PeopleHubDataListenerImpl(viewBoundary))
+ }
}
private class PeopleHubDataListenerImpl(
@@ -92,8 +93,8 @@
private val dataSource: DataSource<@JvmSuppressWildcards PeopleHubModel>
) : DataSource<PeopleHubViewModelFactory> {
- override fun setListener(listener: DataListener<PeopleHubViewModelFactory>) =
- dataSource.setListener(PeopleHubModelListenerImpl(activityStarter, listener))
+ override fun registerListener(listener: DataListener<PeopleHubViewModelFactory>) =
+ dataSource.registerListener(PeopleHubModelListenerImpl(activityStarter, listener))
}
private class PeopleHubModelListenerImpl(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
new file mode 100644
index 0000000..bfd4070
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 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 com.android.systemui.statusbar.notification.people
+
+import android.app.Notification
+import android.service.notification.StatusBarNotification
+import javax.inject.Inject
+import javax.inject.Singleton
+
+interface PeopleNotificationIdentifier {
+ fun isPeopleNotification(sbn: StatusBarNotification): Boolean
+}
+
+@Singleton
+class PeopleNotificationIdentifierImpl @Inject constructor(
+ private val personExtractor: NotificationPersonExtractor
+) : PeopleNotificationIdentifier {
+
+ override fun isPeopleNotification(sbn: StatusBarNotification) =
+ sbn.notification.notificationStyle == Notification.MessagingStyle::class.java ||
+ personExtractor.extractPersonKey(sbn) != null
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/ViewPipeline.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/ViewPipeline.kt
index 33e3bb8..3ca3792 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/ViewPipeline.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/ViewPipeline.kt
@@ -28,10 +28,17 @@
/** Boundary between a View and data pipeline, as seen by the View. */
interface DataSource<out T> {
- fun setListener(listener: DataListener<T>)
+ fun registerListener(listener: DataListener<T>): Subscription
+}
+
+/** Represents a registration with a [DataSource]. */
+interface Subscription {
+ /** Removes the previously registered [DataListener] from the [DataSource] */
+ fun unsubscribe()
}
/** Transform all data coming out of this [DataSource] using the given [mapper]. */
fun <S, T> DataSource<S>.map(mapper: (S) -> T): DataSource<T> = object : DataSource<T> {
- override fun setListener(listener: DataListener<T>) = setListener(listener.contraMap(mapper))
+ override fun registerListener(listener: DataListener<T>) =
+ registerListener(listener.contraMap(mapper))
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
index bd87d77..54d4066 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
@@ -131,6 +131,7 @@
}
mInitialized = true;
reinflateViews(layoutInflater);
+ mPeopleHubViewAdapter.bindView(mPeopleHubViewBoundary);
mConfigurationController.addCallback(mConfigurationListener);
}
@@ -172,10 +173,6 @@
if (oldPeopleHubPos != -1) {
mParent.addView(mPeopleHubView, oldPeopleHubPos);
}
-
- if (!mInitialized) {
- mPeopleHubViewAdapter.bindView(mPeopleHubViewBoundary);
- }
}
/** Listener for when the "clear all" buttton is clciked on the gentle notification header. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index b9516cf..bd3d848 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -53,10 +53,10 @@
*/
public class BiometricUnlockController extends KeyguardUpdateMonitorCallback {
- private static final String TAG = "BiometricUnlockController";
+ private static final String TAG = "BiometricUnlockCtrl";
private static final boolean DEBUG_BIO_WAKELOCK = KeyguardConstants.DEBUG_BIOMETRIC_WAKELOCK;
private static final long BIOMETRIC_WAKELOCK_TIMEOUT_MS = 15 * 1000;
- private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock wakelock";
+ private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock:wakelock";
@IntDef(prefix = { "MODE_" }, value = {
MODE_NONE,
@@ -132,7 +132,7 @@
private final KeyguardBypassController mKeyguardBypassController;
private PowerManager.WakeLock mWakeLock;
private final KeyguardUpdateMonitor mUpdateMonitor;
- private final DozeParameters mDozeParameters;
+ private DozeParameters mDozeParameters;
private final KeyguardStateController mKeyguardStateController;
private final StatusBarWindowController mStatusBarWindowController;
private final Context mContext;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 50d33a7..bc48235 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -24,7 +24,6 @@
import android.provider.Settings;
import android.util.MathUtils;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.MainResources;
import com.android.systemui.doze.AlwaysOnDisplayPolicy;
@@ -188,12 +187,7 @@
return;
}
mControlScreenOffAnimation = controlScreenOffAnimation;
- getPowerManager().setDozeAfterScreenOff(!controlScreenOffAnimation);
- }
-
- @VisibleForTesting
- protected PowerManager getPowerManager() {
- return mPowerManager;
+ mPowerManager.setDozeAfterScreenOff(!controlScreenOffAnimation);
}
private boolean getBoolean(String propName, int resId) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index fe3c04e..1ecc489 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -29,10 +29,12 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import javax.inject.Inject;
+import javax.inject.Singleton;
/**
* Controller which handles all the doze animations of the scrims.
*/
+@Singleton
public class DozeScrimController implements StateListener {
private static final String TAG = "DozeScrimController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
new file mode 100644
index 0000000..2854355
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -0,0 +1,465 @@
+/*
+ * Copyright (C) 2019 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 com.android.systemui.statusbar.phone;
+
+import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
+import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.doze.DozeEvent;
+import com.android.systemui.doze.DozeHost;
+import com.android.systemui.doze.DozeLog;
+import com.android.systemui.doze.DozeReceiver;
+import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.PulseExpansionHandler;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+
+import java.util.ArrayList;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import dagger.Lazy;
+
+/**
+ * Implementation of DozeHost for SystemUI.
+ */
+@Singleton
+public final class DozeServiceHost implements DozeHost {
+ private static final String TAG = "DozeServiceHost";
+ private final ArrayList<Callback> mCallbacks = new ArrayList<>();
+ private final DozeLog mDozeLog;
+ private final PowerManager mPowerManager;
+ private boolean mAnimateWakeup;
+ private boolean mAnimateScreenOff;
+ private boolean mIgnoreTouchWhilePulsing;
+ private Runnable mPendingScreenOffCallback;
+ @VisibleForTesting
+ boolean mWakeLockScreenPerformsAuth = SystemProperties.getBoolean(
+ "persist.sysui.wake_performs_auth", true);
+ private boolean mDozingRequested;
+ private boolean mDozing;
+ private boolean mPulsing;
+ private WakefulnessLifecycle mWakefulnessLifecycle;
+ private final SysuiStatusBarStateController mStatusBarStateController;
+ private final DeviceProvisionedController mDeviceProvisionedController;
+ private final HeadsUpManagerPhone mHeadsUpManagerPhone;
+ private final BatteryController mBatteryController;
+ private final ScrimController mScrimController;
+ private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
+ private BiometricUnlockController mBiometricUnlockController;
+ private final KeyguardViewMediator mKeyguardViewMediator;
+ private final AssistManager mAssistManager;
+ private final DozeScrimController mDozeScrimController;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final VisualStabilityManager mVisualStabilityManager;
+ private final PulseExpansionHandler mPulseExpansionHandler;
+ private final StatusBarWindowController mStatusBarWindowController;
+ private final NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
+ private NotificationIconAreaController mNotificationIconAreaController;
+ private StatusBarWindowViewController mStatusBarWindowViewController;
+ private StatusBarWindowView mStatusBarWindow;
+ private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private NotificationPanelView mNotificationPanel;
+ private View mAmbientIndicationContainer;
+ private StatusBar mStatusBar;
+
+ @Inject
+ public DozeServiceHost(DozeLog dozeLog, PowerManager powerManager,
+ WakefulnessLifecycle wakefulnessLifecycle,
+ SysuiStatusBarStateController statusBarStateController,
+ DeviceProvisionedController deviceProvisionedController,
+ HeadsUpManagerPhone headsUpManagerPhone, BatteryController batteryController,
+ ScrimController scrimController,
+ Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
+ KeyguardViewMediator keyguardViewMediator,
+ AssistManager assistManager,
+ DozeScrimController dozeScrimController, KeyguardUpdateMonitor keyguardUpdateMonitor,
+ VisualStabilityManager visualStabilityManager,
+ PulseExpansionHandler pulseExpansionHandler,
+ StatusBarWindowController statusBarWindowController,
+ NotificationWakeUpCoordinator notificationWakeUpCoordinator) {
+ super();
+ mDozeLog = dozeLog;
+ mPowerManager = powerManager;
+ mWakefulnessLifecycle = wakefulnessLifecycle;
+ mStatusBarStateController = statusBarStateController;
+ mDeviceProvisionedController = deviceProvisionedController;
+ mHeadsUpManagerPhone = headsUpManagerPhone;
+ mBatteryController = batteryController;
+ mScrimController = scrimController;
+ mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
+ mKeyguardViewMediator = keyguardViewMediator;
+ mAssistManager = assistManager;
+ mDozeScrimController = dozeScrimController;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mVisualStabilityManager = visualStabilityManager;
+ mPulseExpansionHandler = pulseExpansionHandler;
+ mStatusBarWindowController = statusBarWindowController;
+ mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
+ }
+
+ // TODO: we should try to not pass status bar in here if we can avoid it.
+
+ /**
+ * Initialize instance with objects only available later during execution.
+ */
+ public void initialize(StatusBar statusBar,
+ NotificationIconAreaController notificationIconAreaController,
+ StatusBarWindowViewController statusBarWindowViewController,
+ StatusBarWindowView statusBarWindow,
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ NotificationPanelView notificationPanel, View ambientIndicationContainer) {
+ mStatusBar = statusBar;
+ mNotificationIconAreaController = notificationIconAreaController;
+ mStatusBarWindowViewController = statusBarWindowViewController;
+ mStatusBarWindow = statusBarWindow;
+ mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mNotificationPanel = notificationPanel;
+ mAmbientIndicationContainer = ambientIndicationContainer;
+ mBiometricUnlockController = mBiometricUnlockControllerLazy.get();
+ }
+
+ @Override
+ public String toString() {
+ return "PSB.DozeServiceHost[mCallbacks=" + mCallbacks.size() + "]";
+ }
+
+ void firePowerSaveChanged(boolean active) {
+ for (Callback callback : mCallbacks) {
+ callback.onPowerSaveChanged(active);
+ }
+ }
+
+ void fireNotificationPulse(NotificationEntry entry) {
+ Runnable pulseSuppressedListener = () -> {
+ entry.setPulseSuppressed(true);
+ mNotificationIconAreaController.updateAodNotificationIcons();
+ };
+ for (Callback callback : mCallbacks) {
+ callback.onNotificationAlerted(pulseSuppressedListener);
+ }
+ }
+
+ boolean getDozingRequested() {
+ return mDozingRequested;
+ }
+
+ boolean isPulsing() {
+ return mPulsing;
+ }
+
+
+ @Override
+ public void addCallback(@NonNull Callback callback) {
+ mCallbacks.add(callback);
+ }
+
+ @Override
+ public void removeCallback(@NonNull Callback callback) {
+ mCallbacks.remove(callback);
+ }
+
+ @Override
+ public void startDozing() {
+ if (!mDozingRequested) {
+ mDozingRequested = true;
+ mDozeLog.traceDozing(mDozing);
+ updateDozing();
+ mStatusBar.updateIsKeyguard();
+ }
+ }
+
+ void updateDozing() {
+ // When in wake-and-unlock while pulsing, keep dozing state until fully unlocked.
+ boolean
+ dozing =
+ mDozingRequested && mStatusBarStateController.getState() == StatusBarState.KEYGUARD
+ || mBiometricUnlockController.getMode()
+ == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
+ // When in wake-and-unlock we may not have received a change to StatusBarState
+ // but we still should not be dozing, manually set to false.
+ if (mBiometricUnlockController.getMode()
+ == BiometricUnlockController.MODE_WAKE_AND_UNLOCK) {
+ dozing = false;
+ }
+
+
+ mStatusBarStateController.setIsDozing(dozing);
+ }
+
+ @Override
+ public void pulseWhileDozing(@NonNull PulseCallback callback, int reason) {
+ if (reason == DozeEvent.PULSE_REASON_SENSOR_LONG_PRESS) {
+ mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
+ "com.android.systemui:LONG_PRESS");
+ mAssistManager.startAssist(new Bundle());
+ return;
+ }
+
+ if (reason == DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) {
+ mScrimController.setWakeLockScreenSensorActive(true);
+ }
+
+ if (reason == DozeEvent.PULSE_REASON_DOCKING && mStatusBarWindow != null) {
+ mStatusBarWindowViewController.suppressWakeUpGesture(true);
+ }
+
+ boolean passiveAuthInterrupt = reason == DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN
+ && mWakeLockScreenPerformsAuth;
+ // Set the state to pulsing, so ScrimController will know what to do once we ask it to
+ // execute the transition. The pulse callback will then be invoked when the scrims
+ // are black, indicating that StatusBar is ready to present the rest of the UI.
+ mPulsing = true;
+ mDozeScrimController.pulse(new PulseCallback() {
+ @Override
+ public void onPulseStarted() {
+ callback.onPulseStarted();
+ mStatusBar.updateNotificationPanelTouchState();
+ setPulsing(true);
+ }
+
+ @Override
+ public void onPulseFinished() {
+ mPulsing = false;
+ callback.onPulseFinished();
+ mStatusBar.updateNotificationPanelTouchState();
+ mScrimController.setWakeLockScreenSensorActive(false);
+ if (mStatusBarWindow != null) {
+ mStatusBarWindowViewController.suppressWakeUpGesture(false);
+ }
+ setPulsing(false);
+ }
+
+ private void setPulsing(boolean pulsing) {
+ mStatusBarStateController.setPulsing(pulsing);
+ mStatusBarKeyguardViewManager.setPulsing(pulsing);
+ mKeyguardViewMediator.setPulsing(pulsing);
+ mNotificationPanel.setPulsing(pulsing);
+ mVisualStabilityManager.setPulsing(pulsing);
+ mStatusBarWindowViewController.setPulsing(pulsing);
+ mIgnoreTouchWhilePulsing = false;
+ if (mKeyguardUpdateMonitor != null && passiveAuthInterrupt) {
+ mKeyguardUpdateMonitor.onAuthInterruptDetected(pulsing /* active */);
+ }
+ mStatusBar.updateScrimController();
+ mPulseExpansionHandler.setPulsing(pulsing);
+ mNotificationWakeUpCoordinator.setPulsing(pulsing);
+ }
+ }, reason);
+ // DozeScrimController is in pulse state, now let's ask ScrimController to start
+ // pulsing and draw the black frame, if necessary.
+ mStatusBar.updateScrimController();
+ }
+
+ @Override
+ public void stopDozing() {
+ if (mDozingRequested) {
+ mDozingRequested = false;
+ mDozeLog.traceDozing(mDozing);
+ updateDozing();
+ }
+ }
+
+ @Override
+ public void onIgnoreTouchWhilePulsing(boolean ignore) {
+ if (ignore != mIgnoreTouchWhilePulsing) {
+ mDozeLog.tracePulseTouchDisabledByProx(ignore);
+ }
+ mIgnoreTouchWhilePulsing = ignore;
+ if (mDozing && ignore) {
+ mStatusBarWindowViewController.cancelCurrentTouch();
+ }
+ }
+
+ @Override
+ public void dozeTimeTick() {
+ mNotificationPanel.dozeTimeTick();
+ if (mAmbientIndicationContainer instanceof DozeReceiver) {
+ ((DozeReceiver) mAmbientIndicationContainer).dozeTimeTick();
+ }
+ }
+
+ @Override
+ public boolean isPowerSaveActive() {
+ return mBatteryController.isAodPowerSave();
+ }
+
+ @Override
+ public boolean isPulsingBlocked() {
+ return mBiometricUnlockController.getMode()
+ == BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
+ }
+
+ @Override
+ public boolean isProvisioned() {
+ return mDeviceProvisionedController.isDeviceProvisioned()
+ && mDeviceProvisionedController.isCurrentUserSetup();
+ }
+
+ @Override
+ public boolean isBlockingDoze() {
+ if (mBiometricUnlockController.hasPendingAuthentication()) {
+ Log.i(StatusBar.TAG, "Blocking AOD because fingerprint has authenticated");
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void extendPulse(int reason) {
+ if (reason == DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) {
+ mScrimController.setWakeLockScreenSensorActive(true);
+ }
+ if (mDozeScrimController.isPulsing() && mHeadsUpManagerPhone.hasNotifications()) {
+ mHeadsUpManagerPhone.extendHeadsUp();
+ } else {
+ mDozeScrimController.extendPulse();
+ }
+ }
+
+ @Override
+ public void stopPulsing() {
+ if (mDozeScrimController.isPulsing()) {
+ mDozeScrimController.pulseOutNow();
+ }
+ }
+
+ @Override
+ public void setAnimateWakeup(boolean animateWakeup) {
+ if (mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_AWAKE
+ || mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_WAKING) {
+ // Too late to change the wakeup animation.
+ return;
+ }
+ mAnimateWakeup = animateWakeup;
+ }
+
+ @Override
+ public void setAnimateScreenOff(boolean animateScreenOff) {
+ mAnimateScreenOff = animateScreenOff;
+ }
+
+ @Override
+ public void onSlpiTap(float screenX, float screenY) {
+ if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null
+ && mAmbientIndicationContainer.getVisibility() == View.VISIBLE) {
+ int[] locationOnScreen = new int[2];
+ mAmbientIndicationContainer.getLocationOnScreen(locationOnScreen);
+ float viewX = screenX - locationOnScreen[0];
+ float viewY = screenY - locationOnScreen[1];
+ if (0 <= viewX && viewX <= mAmbientIndicationContainer.getWidth()
+ && 0 <= viewY && viewY <= mAmbientIndicationContainer.getHeight()) {
+
+ // Dispatch a tap
+ long now = SystemClock.elapsedRealtime();
+ MotionEvent ev = MotionEvent.obtain(
+ now, now, MotionEvent.ACTION_DOWN, screenX, screenY, 0);
+ mAmbientIndicationContainer.dispatchTouchEvent(ev);
+ ev.recycle();
+ ev = MotionEvent.obtain(
+ now, now, MotionEvent.ACTION_UP, screenX, screenY, 0);
+ mAmbientIndicationContainer.dispatchTouchEvent(ev);
+ ev.recycle();
+ }
+ }
+ }
+
+ @Override
+ public void setDozeScreenBrightness(int value) {
+ mStatusBarWindowController.setDozeScreenBrightness(value);
+ }
+
+ @Override
+ public void setAodDimmingScrim(float scrimOpacity) {
+ mScrimController.setAodFrontScrimAlpha(scrimOpacity);
+ }
+
+
+
+ @Override
+ public void prepareForGentleSleep(Runnable onDisplayOffCallback) {
+ if (mPendingScreenOffCallback != null) {
+ Log.w(TAG, "Overlapping onDisplayOffCallback. Ignoring previous one.");
+ }
+ mPendingScreenOffCallback = onDisplayOffCallback;
+ mStatusBar.updateScrimController();
+ }
+
+ @Override
+ public void cancelGentleSleep() {
+ mPendingScreenOffCallback = null;
+ if (mScrimController.getState() == ScrimState.OFF) {
+ mStatusBar.updateScrimController();
+ }
+ }
+
+ /**
+ * When the dozing host is waiting for scrims to fade out to change the display state.
+ */
+ boolean hasPendingScreenOffCallback() {
+ return mPendingScreenOffCallback != null;
+ }
+
+ /**
+ * Executes an nullifies the pending display state callback.
+ *
+ * @see #hasPendingScreenOffCallback()
+ * @see #prepareForGentleSleep(Runnable)
+ */
+ void executePendingScreenOffCallback() {
+ if (mPendingScreenOffCallback == null) {
+ return;
+ }
+ mPendingScreenOffCallback.run();
+ mPendingScreenOffCallback = null;
+ }
+
+ boolean shouldAnimateWakeup() {
+ return mAnimateWakeup;
+ }
+
+ boolean shouldAnimateScreenOff() {
+ return mAnimateScreenOff;
+ }
+
+ public void setDozing(boolean dozing) {
+ mDozing = dozing;
+ }
+
+ boolean getIgnoreTouchWhilePulsing() {
+ return mIgnoreTouchWhilePulsing;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 6064fbe..35039a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -60,11 +60,13 @@
import java.util.function.Consumer;
import javax.inject.Inject;
+import javax.inject.Singleton;
/**
* Controls both the scrim behind the notifications and in front of the notifications (when a
* security method gets shown).
*/
+@Singleton
public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnColorsChangedListener,
Dumpable {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 4e0af64..b8adfea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -50,7 +50,6 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
@@ -157,10 +156,8 @@
import com.android.systemui.charging.WirelessChargingAnimation;
import com.android.systemui.classifier.FalsingLog;
import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.doze.DozeEvent;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
-import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.fragments.ExtensionFragmentListener;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.keyguard.KeyguardSliceProvider;
@@ -347,7 +344,7 @@
/**
* The {@link StatusBarState} of the status bar.
*/
- protected int mState;
+ protected int mState; // TODO: remove this. Just use StatusBarStateController
protected boolean mBouncerShowing;
private PhoneStatusBarPolicy mIconPolicy;
@@ -373,7 +370,7 @@
protected StatusBarWindowController mStatusBarWindowController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@VisibleForTesting
- DozeServiceHost mDozeServiceHost = new DozeServiceHost();
+ DozeServiceHost mDozeServiceHost;
private boolean mWakeUpComingFromTouch;
private PointF mWakeUpTouchLocation;
@@ -493,7 +490,6 @@
private final UiOffloadThread mUiOffloadThread;
protected boolean mDozing;
- private boolean mDozingRequested;
private final NotificationMediaManager mMediaManager;
private final NotificationLockscreenUserManager mLockscreenUserManager;
@@ -612,7 +608,6 @@
private ActivityLaunchAnimator mActivityLaunchAnimator;
protected StatusBarNotificationPresenter mPresenter;
private NotificationActivityStarter mNotificationActivityStarter;
- private boolean mPulsing;
private final BubbleController mBubbleController;
private final BubbleController.BubbleExpandListener mBubbleExpandListener;
@@ -693,7 +688,10 @@
DozeParameters dozeParameters,
ScrimController scrimController,
Lazy<LockscreenWallpaper> lockscreenWallpaperLazy,
- Lazy<BiometricUnlockController> biometricUnlockControllerLazy) {
+ Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
+ DozeServiceHost dozeServiceHost,
+ PowerManager powerManager,
+ DozeScrimController dozeScrimController) {
super(context);
mFeatureFlags = featureFlags;
mLightBarController = lightBarController;
@@ -750,9 +748,12 @@
mStatusBarWindowController = statusBarWindowController;
mStatusBarWindowViewControllerBuilder = statusBarWindowViewControllerBuilder;
mNotifLog = notifLog;
+ mDozeServiceHost = dozeServiceHost;
+ mPowerManager = powerManager;
mDozeParameters = dozeParameters;
mScrimController = scrimController;
mLockscreenWallpaperLazy = lockscreenWallpaperLazy;
+ mDozeScrimController = dozeScrimController;
mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
mBubbleExpandListener =
@@ -805,7 +806,6 @@
mAccessibilityManager = (AccessibilityManager)
mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
- mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -905,6 +905,9 @@
startKeyguard();
mKeyguardUpdateMonitor.registerCallback(mUpdateCallback);
+ mDozeServiceHost.initialize(this, mNotificationIconAreaController,
+ mStatusBarWindowViewController, mStatusBarWindow, mStatusBarKeyguardViewManager,
+ mNotificationPanel, mAmbientIndicationContainer);
putComponent(DozeHost.class, mDozeServiceHost);
mScreenPinningRequest = new ScreenPinningRequest(mContext);
@@ -1069,7 +1072,6 @@
mNotificationPanel.initDependencies(this, mGroupManager, mNotificationShelf,
mHeadsUpManager, mNotificationIconAreaController, mScrimController);
- mDozeScrimController = new DozeScrimController(mDozeParameters, mDozeLog);
BackDropView backdrop = mStatusBarWindow.findViewById(R.id.backdrop);
mMediaManager.setup(backdrop, backdrop.findViewById(R.id.backdrop_front),
@@ -1730,7 +1732,7 @@
if (isDozing() && isHeadsUp) {
entry.setPulseSuppressed(false);
mDozeServiceHost.fireNotificationPulse(entry);
- if (mPulsing) {
+ if (mDozeServiceHost.isPulsing()) {
mDozeScrimController.cancelPendingPulseTimeout();
}
}
@@ -1762,7 +1764,7 @@
}
public boolean isPulsing() {
- return mPulsing;
+ return mDozeServiceHost.isPulsing();
}
public boolean hideStatusBarIconsWhenExpanded() {
@@ -2825,7 +2827,7 @@
if (mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_ASLEEP
&& mKeyguardStateController.canDismissLockScreen()
&& !mStatusBarStateController.leaveOpenOnKeyguardHide()
- && isPulsing()) {
+ && mDozeServiceHost.isPulsing()) {
// Reuse the biometric wake-and-unlock transition if we dismiss keyguard from a pulse.
// TODO: Factor this transition out of BiometricUnlockController.
mBiometricUnlockController.startWakeAndUnlock(
@@ -3156,7 +3158,7 @@
return mState == StatusBarState.FULLSCREEN_USER_SWITCHER;
}
- private boolean updateIsKeyguard() {
+ boolean updateIsKeyguard() {
boolean wakeAndUnlocking = mBiometricUnlockController.getMode()
== BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
@@ -3164,8 +3166,8 @@
// there's no surface we can show to the user. Note that the device goes fully interactive
// late in the transition, so we also allow the device to start dozing once the screen has
// turned off fully.
- boolean keyguardForDozing = mDozingRequested &&
- (!mDeviceInteractive || isGoingToSleep() && (isScreenFullyOff() || mIsKeyguard));
+ boolean keyguardForDozing = mDozeServiceHost.getDozingRequested()
+ && (!mDeviceInteractive || isGoingToSleep() && (isScreenFullyOff() || mIsKeyguard));
boolean shouldBeKeyguard = (mStatusBarStateController.isKeyguardRequested()
|| keyguardForDozing) && !wakeAndUnlocking;
if (keyguardForDozing) {
@@ -3581,7 +3583,7 @@
public void onStateChanged(int newState) {
mState = newState;
updateReportRejectedTouchVisibility();
- updateDozing();
+ mDozeServiceHost.updateDozing();
updateTheme();
mNavigationBarController.touchAutoDim(mDisplayId);
Trace.beginSection("StatusBar#updateKeyguardState");
@@ -3619,36 +3621,23 @@
public void onDozingChanged(boolean isDozing) {
Trace.beginSection("StatusBar#updateDozing");
mDozing = isDozing;
+ mDozeServiceHost.setDozing(mDozing);
// Collapse the notification panel if open
- boolean dozingAnimated = mDozingRequested && mDozeParameters.shouldControlScreenOff();
+ boolean dozingAnimated = mDozeServiceHost.getDozingRequested()
+ && mDozeParameters.shouldControlScreenOff();
mNotificationPanel.resetViews(dozingAnimated);
updateQsExpansionEnabled();
mKeyguardViewMediator.setDozing(mDozing);
mEntryManager.updateNotifications("onDozingChanged");
- updateDozingState();
+ mDozeServiceHost.updateDozing();
updateScrimController();
updateReportRejectedTouchVisibility();
Trace.endSection();
}
- private void updateDozing() {
- // When in wake-and-unlock while pulsing, keep dozing state until fully unlocked.
- boolean dozing = mDozingRequested && mState == StatusBarState.KEYGUARD
- || mBiometricUnlockController.getMode()
- == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
- // When in wake-and-unlock we may not have received a change to mState
- // but we still should not be dozing, manually set to false.
- if (mBiometricUnlockController.getMode() ==
- BiometricUnlockController.MODE_WAKE_AND_UNLOCK) {
- dozing = false;
- }
-
- mStatusBarStateController.setIsDozing(dozing);
- }
-
private void updateKeyguardState() {
mKeyguardStateController.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(),
mStatusBarKeyguardViewManager.isOccluded());
@@ -3873,10 +3862,11 @@
* collapse the panel after we expanded it, and thus we would end up with a blank
* Keyguard.
*/
- private void updateNotificationPanelTouchState() {
+ void updateNotificationPanelTouchState() {
boolean goingToSleepWithoutAnimation = isGoingToSleep()
&& !mDozeParameters.shouldControlScreenOff();
- boolean disabled = (!mDeviceInteractive && !mPulsing) || goingToSleepWithoutAnimation;
+ boolean disabled = (!mDeviceInteractive && !mDozeServiceHost.isPulsing())
+ || goingToSleepWithoutAnimation;
mNotificationPanel.setTouchAndAnimationDisabled(disabled);
mNotificationIconAreaController.setAnimationsEnabled(!disabled);
}
@@ -4026,7 +4016,7 @@
}
public void notifyBiometricAuthModeChanged() {
- updateDozing();
+ mDozeServiceHost.updateDozing();
updateScrimController();
mStatusBarWindowViewController.onBiometricAuthModeChanged(
mBiometricUnlockController.isWakeAndUnlock(),
@@ -4062,7 +4052,7 @@
mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
} else if (mBrightnessMirrorVisible) {
mScrimController.transitionTo(ScrimState.BRIGHTNESS_MIRROR);
- } else if (isPulsing()) {
+ } else if (mDozeServiceHost.isPulsing()) {
mScrimController.transitionTo(ScrimState.PULSING,
mDozeScrimController.getScrimCallback());
} else if (mDozeServiceHost.hasPendingScreenOffCallback()) {
@@ -4092,295 +4082,8 @@
return mStatusBarKeyguardViewManager.isShowing();
}
- @VisibleForTesting
- final class DozeServiceHost implements DozeHost {
- private final ArrayList<Callback> mCallbacks = new ArrayList<>();
- private boolean mAnimateWakeup;
- private boolean mAnimateScreenOff;
- private boolean mIgnoreTouchWhilePulsing;
- private Runnable mPendingScreenOffCallback;
- @VisibleForTesting
- boolean mWakeLockScreenPerformsAuth = SystemProperties.getBoolean(
- "persist.sysui.wake_performs_auth", true);
-
- @Override
- public String toString() {
- return "PSB.DozeServiceHost[mCallbacks=" + mCallbacks.size() + "]";
- }
-
- public void firePowerSaveChanged(boolean active) {
- for (Callback callback : mCallbacks) {
- callback.onPowerSaveChanged(active);
- }
- }
-
- public void fireNotificationPulse(NotificationEntry entry) {
- Runnable pulseSupressedListener = () -> {
- entry.setPulseSuppressed(true);
- mNotificationIconAreaController.updateAodNotificationIcons();
- };
- for (Callback callback : mCallbacks) {
- callback.onNotificationAlerted(pulseSupressedListener);
- }
- }
-
- @Override
- public void addCallback(@NonNull Callback callback) {
- mCallbacks.add(callback);
- }
-
- @Override
- public void removeCallback(@NonNull Callback callback) {
- mCallbacks.remove(callback);
- }
-
- @Override
- public void startDozing() {
- if (!mDozingRequested) {
- mDozingRequested = true;
- mDozeLog.traceDozing(mDozing);
- updateDozing();
- updateIsKeyguard();
- }
- }
-
- @Override
- public void pulseWhileDozing(@NonNull PulseCallback callback, int reason) {
- if (reason == DozeEvent.PULSE_REASON_SENSOR_LONG_PRESS) {
- mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
- "com.android.systemui:LONG_PRESS");
- startAssist(new Bundle());
- return;
- }
-
- if (reason == DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) {
- mScrimController.setWakeLockScreenSensorActive(true);
- }
-
- if (reason == DozeEvent.PULSE_REASON_DOCKING && mStatusBarWindow != null) {
- mStatusBarWindowViewController.suppressWakeUpGesture(true);
- }
-
- boolean passiveAuthInterrupt = reason == DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN
- && mWakeLockScreenPerformsAuth;
- // Set the state to pulsing, so ScrimController will know what to do once we ask it to
- // execute the transition. The pulse callback will then be invoked when the scrims
- // are black, indicating that StatusBar is ready to present the rest of the UI.
- mPulsing = true;
- mDozeScrimController.pulse(new PulseCallback() {
- @Override
- public void onPulseStarted() {
- callback.onPulseStarted();
- updateNotificationPanelTouchState();
- setPulsing(true);
- }
-
- @Override
- public void onPulseFinished() {
- mPulsing = false;
- callback.onPulseFinished();
- updateNotificationPanelTouchState();
- mScrimController.setWakeLockScreenSensorActive(false);
- if (mStatusBarWindow != null) {
- mStatusBarWindowViewController.suppressWakeUpGesture(false);
- }
- setPulsing(false);
- }
-
- private void setPulsing(boolean pulsing) {
- mStatusBarStateController.setPulsing(pulsing);
- mStatusBarKeyguardViewManager.setPulsing(pulsing);
- mKeyguardViewMediator.setPulsing(pulsing);
- mNotificationPanel.setPulsing(pulsing);
- mVisualStabilityManager.setPulsing(pulsing);
- mStatusBarWindowViewController.setPulsing(pulsing);
- mIgnoreTouchWhilePulsing = false;
- if (mKeyguardUpdateMonitor != null && passiveAuthInterrupt) {
- mKeyguardUpdateMonitor.onAuthInterruptDetected(pulsing /* active */);
- }
- updateScrimController();
- mPulseExpansionHandler.setPulsing(pulsing);
- mWakeUpCoordinator.setPulsing(pulsing);
- }
- }, reason);
- // DozeScrimController is in pulse state, now let's ask ScrimController to start
- // pulsing and draw the black frame, if necessary.
- updateScrimController();
- }
-
- @Override
- public void stopDozing() {
- if (mDozingRequested) {
- mDozingRequested = false;
- mDozeLog.traceDozing(mDozing);
- updateDozing();
- }
- }
-
- @Override
- public void onIgnoreTouchWhilePulsing(boolean ignore) {
- if (ignore != mIgnoreTouchWhilePulsing) {
- mDozeLog.tracePulseTouchDisabledByProx(ignore);
- }
- mIgnoreTouchWhilePulsing = ignore;
- if (isDozing() && ignore) {
- mStatusBarWindowViewController.cancelCurrentTouch();
- }
- }
-
- @Override
- public void dozeTimeTick() {
- mNotificationPanel.dozeTimeTick();
- if (mAmbientIndicationContainer instanceof DozeReceiver) {
- ((DozeReceiver) mAmbientIndicationContainer).dozeTimeTick();
- }
- }
-
- @Override
- public boolean isPowerSaveActive() {
- return mBatteryController.isAodPowerSave();
- }
-
- @Override
- public boolean isPulsingBlocked() {
- return mBiometricUnlockController.getMode()
- == BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
- }
-
- @Override
- public boolean isProvisioned() {
- return mDeviceProvisionedController.isDeviceProvisioned()
- && mDeviceProvisionedController.isCurrentUserSetup();
- }
-
- @Override
- public boolean isBlockingDoze() {
- if (mBiometricUnlockController.hasPendingAuthentication()) {
- Log.i(TAG, "Blocking AOD because fingerprint has authenticated");
- return true;
- }
- return false;
- }
-
- @Override
- public void extendPulse(int reason) {
- if (reason == DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) {
- mScrimController.setWakeLockScreenSensorActive(true);
- }
- if (mDozeScrimController.isPulsing() && mHeadsUpManager.hasNotifications()) {
- mHeadsUpManager.extendHeadsUp();
- } else {
- mDozeScrimController.extendPulse();
- }
- }
-
- @Override
- public void stopPulsing() {
- if (mDozeScrimController.isPulsing()) {
- mDozeScrimController.pulseOutNow();
- }
- }
-
- @Override
- public void setAnimateWakeup(boolean animateWakeup) {
- if (mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_AWAKE
- || mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_WAKING) {
- // Too late to change the wakeup animation.
- return;
- }
- mAnimateWakeup = animateWakeup;
- }
-
- @Override
- public void setAnimateScreenOff(boolean animateScreenOff) {
- mAnimateScreenOff = animateScreenOff;
- }
-
- @Override
- public void onSlpiTap(float screenX, float screenY) {
- if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null
- && mAmbientIndicationContainer.getVisibility() == View.VISIBLE) {
- mAmbientIndicationContainer.getLocationOnScreen(mTmpInt2);
- float viewX = screenX - mTmpInt2[0];
- float viewY = screenY - mTmpInt2[1];
- if (0 <= viewX && viewX <= mAmbientIndicationContainer.getWidth()
- && 0 <= viewY && viewY <= mAmbientIndicationContainer.getHeight()) {
- dispatchTap(mAmbientIndicationContainer, viewX, viewY);
- }
- }
- }
-
- @Override
- public void setDozeScreenBrightness(int value) {
- mStatusBarWindowController.setDozeScreenBrightness(value);
- }
-
- @Override
- public void setAodDimmingScrim(float scrimOpacity) {
- mScrimController.setAodFrontScrimAlpha(scrimOpacity);
- }
-
- @Override
- public void prepareForGentleSleep(Runnable onDisplayOffCallback) {
- if (mPendingScreenOffCallback != null) {
- Log.w(TAG, "Overlapping onDisplayOffCallback. Ignoring previous one.");
- }
- mPendingScreenOffCallback = onDisplayOffCallback;
- updateScrimController();
- }
-
- @Override
- public void cancelGentleSleep() {
- mPendingScreenOffCallback = null;
- if (mScrimController.getState() == ScrimState.OFF) {
- updateScrimController();
- }
- }
-
- /**
- * When the dozing host is waiting for scrims to fade out to change the display state.
- */
- boolean hasPendingScreenOffCallback() {
- return mPendingScreenOffCallback != null;
- }
-
- /**
- * Executes an nullifies the pending display state callback.
- *
- * @see #hasPendingScreenOffCallback()
- * @see #prepareForGentleSleep(Runnable)
- */
- void executePendingScreenOffCallback() {
- if (mPendingScreenOffCallback == null) {
- return;
- }
- mPendingScreenOffCallback.run();
- mPendingScreenOffCallback = null;
- }
-
- private void dispatchTap(View view, float x, float y) {
- long now = SystemClock.elapsedRealtime();
- dispatchTouchEvent(view, x, y, now, MotionEvent.ACTION_DOWN);
- dispatchTouchEvent(view, x, y, now, MotionEvent.ACTION_UP);
- }
-
- private void dispatchTouchEvent(View view, float x, float y, long now, int action) {
- MotionEvent ev = MotionEvent.obtain(now, now, action, x, y, 0 /* meta */);
- view.dispatchTouchEvent(ev);
- ev.recycle();
- }
-
- private boolean shouldAnimateWakeup() {
- return mAnimateWakeup;
- }
-
- public boolean shouldAnimateScreenOff() {
- return mAnimateScreenOff;
- }
- }
-
public boolean shouldIgnoreTouch() {
- return isDozing() && mDozeServiceHost.mIgnoreTouchWhilePulsing;
+ return isDozing() && mDozeServiceHost.getIgnoreTouchWhilePulsing();
}
// Begin Extra BaseStatusBar methods.
@@ -4407,7 +4110,7 @@
private boolean mVisibleToUser;
protected DevicePolicyManager mDevicePolicyManager;
- protected PowerManager mPowerManager;
+ private final PowerManager mPowerManager;
protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
protected KeyguardManager mKeyguardManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionController.java
index cade5dc..3ce6239 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionController.java
@@ -57,7 +57,7 @@
ExtensionBuilder<T> withCallback(Consumer<T> callback);
ExtensionBuilder<T> withUiMode(int mode, Supplier<T> def);
ExtensionBuilder<T> withFeature(String feature, Supplier<T> def);
- Extension build();
+ Extension<T> build();
}
public interface PluginConverter<T, P> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
index fd030d1..eeef726 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
@@ -135,7 +135,7 @@
}
@Override
- public ExtensionController.Extension build() {
+ public ExtensionController.Extension<T> build() {
// Sort items in ascending order
Collections.sort(mExtension.mProducers, Comparator.comparingInt(Item::sortOrder));
mExtension.notifyChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 7b4a7d2..502a9bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -679,8 +679,8 @@
}
};
- InputConnection ic = InputConnectionCompat.createWrapper(
- inputConnection, outAttrs, callback);
+ InputConnection ic = inputConnection == null ? null :
+ InputConnectionCompat.createWrapper(inputConnection, outAttrs, callback);
Context userContext = null;
try {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index ebdf851..bde7ef9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -82,6 +82,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationRowBinder;
import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
import com.android.systemui.statusbar.notification.logging.NotifLog;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -148,8 +149,12 @@
private final CountDownLatch mCountDownLatch;
TestableNotificationEntryManager() {
- super(new NotificationData(mock(NotificationSectionsFeatureManager.class),
- mock(NotifLog.class)), mock(NotifLog.class));
+ super(
+ new NotificationData(
+ mock(NotificationSectionsFeatureManager.class),
+ mock(NotifLog.class),
+ mock(PeopleNotificationIdentifier.class)),
+ mock(NotifLog.class));
mCountDownLatch = new CountDownLatch(1);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
index 9202c51..2435bb9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
@@ -46,6 +46,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationData;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.NotifLog;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
@@ -78,8 +79,10 @@
// TODO: Remove this once EntryManager no longer needs to be mocked
private NotificationData mNotificationData =
- new NotificationData(new NotificationSectionsFeatureManager(
- new DeviceConfigProxyFake(), mContext), mock(NotifLog.class));
+ new NotificationData(
+ new NotificationSectionsFeatureManager(new DeviceConfigProxyFake(), mContext),
+ mock(NotifLog.class),
+ mock(PeopleNotificationIdentifier.class));
private int mNextNotifId = 0;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
index 640984b..dba0174 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
@@ -81,6 +81,7 @@
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment;
import com.android.systemui.statusbar.notification.logging.NotifLog;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.ShadeController;
@@ -639,7 +640,10 @@
public static class TestableNotificationData extends NotificationData {
public TestableNotificationData(NotificationSectionsFeatureManager sectionsFeatureManager) {
- super(sectionsFeatureManager, mock(NotifLog.class));
+ super(
+ sectionsFeatureManager,
+ mock(NotifLog.class),
+ mock(PeopleNotificationIdentifier.class));
}
public static final String OVERRIDE_RANK = "r";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
new file mode 100644
index 0000000..b05172c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2019 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 com.android.systemui.statusbar.phone;
+
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.PowerManager;
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.doze.DozeEvent;
+import com.android.systemui.doze.DozeHost;
+import com.android.systemui.doze.DozeLog;
+import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.PulseExpansionHandler;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.StatusBarStateControllerImpl;
+import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+
+import dagger.Lazy;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DozeServiceHostTest extends SysuiTestCase {
+
+ private DozeServiceHost mDozeServiceHost;
+
+ @Mock private HeadsUpManagerPhone mHeadsUpManager;
+ @Mock private ScrimController mScrimController;
+ @Mock private DozeScrimController mDozeScrimController;
+ @Mock private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
+ @Mock private VisualStabilityManager mVisualStabilityManager;
+ @Mock private KeyguardViewMediator mKeyguardViewMediator;
+ @Mock private StatusBarStateControllerImpl mStatusBarStateController;
+ @Mock private BatteryController mBatteryController;
+ @Mock private DeviceProvisionedController mDeviceProvisionedController;
+ @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock private AssistManager mAssistManager;
+ @Mock private DozeLog mDozeLog;
+ @Mock private PulseExpansionHandler mPulseExpansionHandler;
+ @Mock private NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
+ @Mock private StatusBarWindowController mStatusBarWindowController;
+ @Mock private PowerManager mPowerManager;
+ @Mock private WakefulnessLifecycle mWakefullnessLifecycle;
+ @Mock private StatusBar mStatusBar;
+ @Mock private NotificationIconAreaController mNotificationIconAreaController;
+ @Mock private StatusBarWindowViewController mStatusBarWindowViewController;
+ @Mock private StatusBarWindowView mStatusBarWindow;
+ @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ @Mock private NotificationPanelView mNotificationPanel;
+ @Mock private View mAmbientIndicationContainer;
+ @Mock private BiometricUnlockController mBiometricUnlockController;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ when(mBiometricUnlockControllerLazy.get()).thenReturn(mBiometricUnlockController);
+ mDozeServiceHost = new DozeServiceHost(mDozeLog, mPowerManager, mWakefullnessLifecycle,
+ mStatusBarStateController, mDeviceProvisionedController, mHeadsUpManager,
+ mBatteryController, mScrimController, mBiometricUnlockControllerLazy,
+ mKeyguardViewMediator, mAssistManager, mDozeScrimController, mKeyguardUpdateMonitor,
+ mVisualStabilityManager, mPulseExpansionHandler, mStatusBarWindowController,
+ mNotificationWakeUpCoordinator);
+
+ mDozeServiceHost.initialize(mStatusBar, mNotificationIconAreaController,
+ mStatusBarWindowViewController, mStatusBarWindow, mStatusBarKeyguardViewManager,
+ mNotificationPanel, mAmbientIndicationContainer);
+ }
+
+ @Test
+ public void testStartStopDozing() {
+ when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
+ when(mStatusBarStateController.isKeyguardRequested()).thenReturn(true);
+
+ assertFalse(mDozeServiceHost.getDozingRequested());
+
+ mDozeServiceHost.startDozing();
+ verify(mStatusBarStateController).setIsDozing(eq(true));
+ verify(mStatusBar).updateIsKeyguard();
+
+ mDozeServiceHost.stopDozing();
+ verify(mStatusBarStateController).setIsDozing(eq(false));
+ }
+
+
+ @Test
+ public void testPulseWhileDozing_updatesScrimController() {
+ mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
+ mStatusBar.showKeyguardImpl();
+
+ // Keep track of callback to be able to stop the pulse
+// DozeHost.PulseCallback[] pulseCallback = new DozeHost.PulseCallback[1];
+// doAnswer(invocation -> {
+// pulseCallback[0] = invocation.getArgument(0);
+// return null;
+// }).when(mDozeScrimController).pulse(any(), anyInt());
+
+ // Starting a pulse should change the scrim controller to the pulsing state
+ mDozeServiceHost.pulseWhileDozing(new DozeHost.PulseCallback() {
+ @Override
+ public void onPulseStarted() {
+ }
+
+ @Override
+ public void onPulseFinished() {
+ }
+ }, DozeEvent.PULSE_REASON_NOTIFICATION);
+
+ ArgumentCaptor<DozeHost.PulseCallback> pulseCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(DozeHost.PulseCallback.class);
+
+ verify(mDozeScrimController).pulse(
+ pulseCallbackArgumentCaptor.capture(), eq(DozeEvent.PULSE_REASON_NOTIFICATION));
+ verify(mStatusBar).updateScrimController();
+ reset(mStatusBar);
+
+ pulseCallbackArgumentCaptor.getValue().onPulseFinished();
+ assertFalse(mDozeScrimController.isPulsing());
+ verify(mStatusBar).updateScrimController();
+ }
+
+
+ @Test
+ public void testPulseWhileDozingWithDockingReason_suppressWakeUpGesture() {
+ // Keep track of callback to be able to stop the pulse
+ final DozeHost.PulseCallback[] pulseCallback = new DozeHost.PulseCallback[1];
+ doAnswer(invocation -> {
+ pulseCallback[0] = invocation.getArgument(0);
+ return null;
+ }).when(mDozeScrimController).pulse(any(), anyInt());
+
+ // Starting a pulse while docking should suppress wakeup gesture
+ mDozeServiceHost.pulseWhileDozing(mock(DozeHost.PulseCallback.class),
+ DozeEvent.PULSE_REASON_DOCKING);
+ verify(mStatusBarWindowViewController).suppressWakeUpGesture(eq(true));
+
+ // Ending a pulse should restore wakeup gesture
+ pulseCallback[0].onPulseFinished();
+ verify(mStatusBarWindowViewController).suppressWakeUpGesture(eq(false));
+ }
+
+ @Test
+ public void testPulseWhileDozing_notifyAuthInterrupt() {
+ HashSet<Integer> reasonsWantingAuth = new HashSet<>(
+ Collections.singletonList(DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN));
+ HashSet<Integer> reasonsSkippingAuth = new HashSet<>(
+ Arrays.asList(DozeEvent.PULSE_REASON_INTENT,
+ DozeEvent.PULSE_REASON_NOTIFICATION,
+ DozeEvent.PULSE_REASON_SENSOR_SIGMOTION,
+ DozeEvent.REASON_SENSOR_PICKUP,
+ DozeEvent.REASON_SENSOR_DOUBLE_TAP,
+ DozeEvent.PULSE_REASON_SENSOR_LONG_PRESS,
+ DozeEvent.PULSE_REASON_DOCKING,
+ DozeEvent.REASON_SENSOR_WAKE_UP,
+ DozeEvent.REASON_SENSOR_TAP));
+ HashSet<Integer> reasonsThatDontPulse = new HashSet<>(
+ Arrays.asList(DozeEvent.REASON_SENSOR_PICKUP,
+ DozeEvent.REASON_SENSOR_DOUBLE_TAP,
+ DozeEvent.REASON_SENSOR_TAP));
+
+ doAnswer(invocation -> {
+ DozeHost.PulseCallback callback = invocation.getArgument(0);
+ callback.onPulseStarted();
+ return null;
+ }).when(mDozeScrimController).pulse(any(), anyInt());
+
+ mDozeServiceHost.mWakeLockScreenPerformsAuth = true;
+ for (int i = 0; i < DozeEvent.TOTAL_REASONS; i++) {
+ reset(mKeyguardUpdateMonitor);
+ mDozeServiceHost.pulseWhileDozing(mock(DozeHost.PulseCallback.class), i);
+ if (reasonsWantingAuth.contains(i)) {
+ verify(mKeyguardUpdateMonitor).onAuthInterruptDetected(eq(true));
+ } else if (reasonsSkippingAuth.contains(i) || reasonsThatDontPulse.contains(i)) {
+ verify(mKeyguardUpdateMonitor, never()).onAuthInterruptDetected(eq(true));
+ } else {
+ throw new AssertionError("Reason " + i + " isn't specified as wanting or skipping"
+ + " passive auth. Please consider how this pulse reason should behave.");
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index cff6635..4853f20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -57,6 +57,7 @@
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.collection.NotificationData;
import com.android.systemui.statusbar.notification.logging.NotifLog;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -236,8 +237,11 @@
mock(PluginManager.class),
mock(ShadeController.class),
mock(NotificationLockscreenUserManager.class),
- new NotificationEntryManager(new NotificationData(mock(
- NotificationSectionsFeatureManager.class), mock(NotifLog.class)),
+ new NotificationEntryManager(
+ new NotificationData(
+ mock(NotificationSectionsFeatureManager.class),
+ mock(NotifLog.class),
+ mock(PeopleNotificationIdentifier.class)),
mock(NotifLog.class)),
mock(KeyguardStateController.class),
statusBarStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index f5e92e4..66c01ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -85,8 +85,6 @@
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.doze.DozeEvent;
-import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
@@ -145,9 +143,6 @@
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
import dagger.Lazy;
@@ -231,6 +226,7 @@
@Mock private DozeParameters mDozeParameters;
@Mock private Lazy<LockscreenWallpaper> mLockscreenWallpaperLazy;
@Mock private LockscreenWallpaper mLockscreenWallpaper;
+ @Mock private DozeServiceHost mDozeServiceHost;
@Mock private LinearLayout mLockIconContainer;
@Mock private ViewMediatorCallback mKeyguardVieMediatorCallback;
@@ -365,7 +361,10 @@
mDozeParameters,
mScrimController,
mLockscreenWallpaperLazy,
- mBiometricUnlockControllerLazy);
+ mBiometricUnlockControllerLazy,
+ mDozeServiceHost,
+ mPowerManager,
+ mDozeScrimController);
when(mStatusBarWindowView.findViewById(R.id.lock_icon_container)).thenReturn(
mLockIconContainer);
@@ -388,7 +387,6 @@
mStatusBar.mNotificationIconAreaController = mNotificationIconAreaController;
mStatusBar.mPresenter = mNotificationPresenter;
mStatusBar.mKeyguardIndicationController = mKeyguardIndicationController;
- mStatusBar.mPowerManager = mPowerManager;
mStatusBar.mBarService = mBarService;
mStatusBar.mStackScroller = mStackScroller;
mStatusBar.mStatusBarWindowViewController = mStatusBarWindowViewController;
@@ -757,83 +755,18 @@
mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
mStatusBar.showKeyguardImpl();
- // Keep track of callback to be able to stop the pulse
- DozeHost.PulseCallback[] pulseCallback = new DozeHost.PulseCallback[1];
- doAnswer(invocation -> {
- pulseCallback[0] = invocation.getArgument(0);
- return null;
- }).when(mDozeScrimController).pulse(any(), anyInt());
-
// Starting a pulse should change the scrim controller to the pulsing state
- mStatusBar.mDozeServiceHost.pulseWhileDozing(mock(DozeHost.PulseCallback.class),
- DozeEvent.PULSE_REASON_NOTIFICATION);
+ when(mDozeServiceHost.isPulsing()).thenReturn(true);
+ mStatusBar.updateScrimController();
verify(mScrimController).transitionTo(eq(ScrimState.PULSING), any());
// Ending a pulse should take it back to keyguard state
- pulseCallback[0].onPulseFinished();
+ when(mDozeServiceHost.isPulsing()).thenReturn(false);
+ mStatusBar.updateScrimController();
verify(mScrimController).transitionTo(eq(ScrimState.KEYGUARD));
}
@Test
- public void testPulseWhileDozing_notifyAuthInterrupt() {
- HashSet<Integer> reasonsWantingAuth = new HashSet<>(
- Collections.singletonList(DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN));
- HashSet<Integer> reasonsSkippingAuth = new HashSet<>(
- Arrays.asList(DozeEvent.PULSE_REASON_INTENT,
- DozeEvent.PULSE_REASON_NOTIFICATION,
- DozeEvent.PULSE_REASON_SENSOR_SIGMOTION,
- DozeEvent.REASON_SENSOR_PICKUP,
- DozeEvent.REASON_SENSOR_DOUBLE_TAP,
- DozeEvent.PULSE_REASON_SENSOR_LONG_PRESS,
- DozeEvent.PULSE_REASON_DOCKING,
- DozeEvent.REASON_SENSOR_WAKE_UP,
- DozeEvent.REASON_SENSOR_TAP));
- HashSet<Integer> reasonsThatDontPulse = new HashSet<>(
- Arrays.asList(DozeEvent.REASON_SENSOR_PICKUP,
- DozeEvent.REASON_SENSOR_DOUBLE_TAP,
- DozeEvent.REASON_SENSOR_TAP));
-
- doAnswer(invocation -> {
- DozeHost.PulseCallback callback = invocation.getArgument(0);
- callback.onPulseStarted();
- return null;
- }).when(mDozeScrimController).pulse(any(), anyInt());
-
- mStatusBar.mDozeServiceHost.mWakeLockScreenPerformsAuth = true;
- for (int i = 0; i < DozeEvent.TOTAL_REASONS; i++) {
- reset(mKeyguardUpdateMonitor);
- mStatusBar.mDozeServiceHost.pulseWhileDozing(mock(DozeHost.PulseCallback.class), i);
- if (reasonsWantingAuth.contains(i)) {
- verify(mKeyguardUpdateMonitor).onAuthInterruptDetected(eq(true));
- } else if (reasonsSkippingAuth.contains(i) || reasonsThatDontPulse.contains(i)) {
- verify(mKeyguardUpdateMonitor, never()).onAuthInterruptDetected(eq(true));
- } else {
- throw new AssertionError("Reason " + i + " isn't specified as wanting or skipping"
- + " passive auth. Please consider how this pulse reason should behave.");
- }
- }
- }
-
- @Test
- public void testPulseWhileDozingWithDockingReason_suppressWakeUpGesture() {
- // Keep track of callback to be able to stop the pulse
- final DozeHost.PulseCallback[] pulseCallback = new DozeHost.PulseCallback[1];
- doAnswer(invocation -> {
- pulseCallback[0] = invocation.getArgument(0);
- return null;
- }).when(mDozeScrimController).pulse(any(), anyInt());
-
- // Starting a pulse while docking should suppress wakeup gesture
- mStatusBar.mDozeServiceHost.pulseWhileDozing(mock(DozeHost.PulseCallback.class),
- DozeEvent.PULSE_REASON_DOCKING);
- verify(mStatusBarWindowViewController).suppressWakeUpGesture(eq(true));
-
- // Ending a pulse should restore wakeup gesture
- pulseCallback[0].onPulseFinished();
- verify(mStatusBarWindowViewController).suppressWakeUpGesture(eq(false));
- }
-
- @Test
public void testSetState_changesIsFullScreenUserSwitcherState() {
mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
assertFalse(mStatusBar.isFullScreenUserSwitcherState());
@@ -859,27 +792,17 @@
}
@Test
- public void testStartStopDozing() {
- mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
- when(mStatusBarStateController.isKeyguardRequested()).thenReturn(true);
-
- mStatusBar.mDozeServiceHost.startDozing();
- verify(mStatusBarStateController).setIsDozing(eq(true));
-
- mStatusBar.mDozeServiceHost.stopDozing();
- verify(mStatusBarStateController).setIsDozing(eq(false));
- }
-
- @Test
public void testOnStartedWakingUp_isNotDozing() {
mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
when(mStatusBarStateController.isKeyguardRequested()).thenReturn(true);
- mStatusBar.mDozeServiceHost.startDozing();
- verify(mStatusBarStateController).setIsDozing(eq(true));
+ when(mDozeServiceHost.getDozingRequested()).thenReturn(true);
+ mStatusBar.updateIsKeyguard();
+ // TODO: mNotificationPanelView.expand(false) gets called twice. Should be once.
+ verify(mNotificationPanelView, times(2)).expand(eq(false));
clearInvocations(mNotificationPanelView);
mStatusBar.mWakefulnessObserver.onStartedWakingUp();
- verify(mStatusBarStateController).setIsDozing(eq(false));
+ verify(mDozeServiceHost).stopDozing();
verify(mNotificationPanelView).expand(eq(false));
}
@@ -887,7 +810,8 @@
public void testOnStartedWakingUp_doesNotDismissBouncer_whenPulsing() {
mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
when(mStatusBarStateController.isKeyguardRequested()).thenReturn(true);
- mStatusBar.mDozeServiceHost.startDozing();
+ when(mDozeServiceHost.getDozingRequested()).thenReturn(true);
+ mStatusBar.updateIsKeyguard();
clearInvocations(mNotificationPanelView);
mStatusBar.setBouncerShowing(true);
diff --git a/services/core/java/com/android/server/GnssManagerService.java b/services/core/java/com/android/server/GnssManagerService.java
index 44a8234..274e2f1 100644
--- a/services/core/java/com/android/server/GnssManagerService.java
+++ b/services/core/java/com/android/server/GnssManagerService.java
@@ -18,6 +18,7 @@
import android.Manifest;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.content.Context;
@@ -300,7 +301,7 @@
* @return true if callback is successfully added, false otherwise
*/
public boolean addGnssBatchingCallback(IBatchedLocationCallback callback, String packageName,
- @NonNull String listenerIdentity) {
+ @Nullable String featureId, @NonNull String listenerIdentity) {
mContext.enforceCallingPermission(
android.Manifest.permission.LOCATION_HARDWARE,
"Location Hardware permission not granted to access hardware batching");
@@ -319,7 +320,7 @@
CallerIdentity callerIdentity =
new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(), packageName,
- listenerIdentity);
+ featureId, listenerIdentity);
synchronized (mGnssBatchingLock) {
mGnssBatchingCallback = callback;
mGnssBatchingDeathCallback =
@@ -497,6 +498,7 @@
private <TListener extends IInterface> boolean addGnssDataListenerLocked(
TListener listener,
String packageName,
+ @Nullable String featureId,
@NonNull String listenerIdentifier,
RemoteListenerHelper<TListener> gnssDataProvider,
ArrayMap<IBinder,
@@ -517,7 +519,7 @@
CallerIdentity callerIdentity =
new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(), packageName,
- listenerIdentifier);
+ featureId, listenerIdentifier);
LinkedListener<TListener> linkedListener =
new LocationManagerServiceUtils.LinkedListener<>(
listener, listenerIdentifier, callerIdentity, binderDeathCallback);
@@ -605,11 +607,13 @@
* @param packageName name of requesting package
* @return true if listener is successfully registered, false otherwise
*/
- public boolean registerGnssStatusCallback(IGnssStatusListener listener, String packageName) {
+ public boolean registerGnssStatusCallback(IGnssStatusListener listener, String packageName,
+ @Nullable String featureId) {
synchronized (mGnssStatusListeners) {
return addGnssDataListenerLocked(
listener,
packageName,
+ featureId,
"Gnss status",
mGnssStatusProvider,
mGnssStatusListeners,
@@ -636,12 +640,13 @@
* @return true if listener is successfully added, false otherwise
*/
public boolean addGnssMeasurementsListener(
- IGnssMeasurementsListener listener, String packageName,
+ IGnssMeasurementsListener listener, String packageName, @Nullable String featureId,
@NonNull String listenerIdentifier) {
synchronized (mGnssMeasurementsListeners) {
return addGnssDataListenerLocked(
listener,
packageName,
+ featureId,
listenerIdentifier,
mGnssMeasurementsProvider,
mGnssMeasurementsListeners,
@@ -695,11 +700,12 @@
*/
public boolean addGnssNavigationMessageListener(
IGnssNavigationMessageListener listener, String packageName,
- @NonNull String listenerIdentifier) {
+ @Nullable String featureId, @NonNull String listenerIdentifier) {
synchronized (mGnssNavigationMessageListeners) {
return addGnssDataListenerLocked(
listener,
packageName,
+ featureId,
listenerIdentifier,
mGnssNavigationMessageProvider,
mGnssNavigationMessageListeners,
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index ad3f502..0a63bf8 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -1233,9 +1233,9 @@
PowerManager.WakeLock mWakeLock;
private Receiver(ILocationListener listener, PendingIntent intent, int pid, int uid,
- String packageName, WorkSource workSource, boolean hideFromAppOps,
- @NonNull String listenerIdentifier) {
- super(new CallerIdentity(uid, pid, packageName, listenerIdentifier),
+ String packageName, @Nullable String featureId, WorkSource workSource,
+ boolean hideFromAppOps, @NonNull String listenerIdentifier) {
+ super(new CallerIdentity(uid, pid, packageName, featureId, listenerIdentifier),
"LocationListener");
mListener = listener;
mPendingIntent = intent;
@@ -1360,7 +1360,8 @@
if (!currentlyMonitoring) {
if (allowMonitoring) {
return mAppOps.startOpNoThrow(op, mCallerIdentity.mUid,
- mCallerIdentity.mPackageName) == AppOpsManager.MODE_ALLOWED;
+ mCallerIdentity.mPackageName, false, mCallerIdentity.mFeatureId, null)
+ == AppOpsManager.MODE_ALLOWED;
}
} else {
if (!allowMonitoring
@@ -1545,11 +1546,11 @@
@Override
public boolean addGnssBatchingCallback(IBatchedLocationCallback callback, String packageName,
- String listenerIdentifier) {
+ String featureId, String listenerIdentifier) {
Preconditions.checkNotNull(listenerIdentifier);
return mGnssManagerService == null ? false : mGnssManagerService.addGnssBatchingCallback(
- callback, packageName, listenerIdentifier);
+ callback, packageName, featureId, listenerIdentifier);
}
@Override
@@ -1712,10 +1713,10 @@
}
private boolean reportLocationAccessNoThrow(int pid, int uid, String packageName,
- int allowedResolutionLevel, @Nullable String message) {
+ @Nullable String featureId, int allowedResolutionLevel, @Nullable String message) {
int op = resolutionLevelToOp(allowedResolutionLevel);
if (op >= 0) {
- if (mAppOps.noteOpNoThrow(op, uid, packageName, null, message)
+ if (mAppOps.noteOpNoThrow(op, uid, packageName, featureId, message)
!= AppOpsManager.MODE_ALLOWED) {
return false;
}
@@ -2149,12 +2150,12 @@
@GuardedBy("mLock")
private Receiver getReceiverLocked(ILocationListener listener, int pid, int uid,
- String packageName, WorkSource workSource, boolean hideFromAppOps,
- @NonNull String listenerIdentifier) {
+ String packageName, @Nullable String featureId, WorkSource workSource,
+ boolean hideFromAppOps, @NonNull String listenerIdentifier) {
IBinder binder = listener.asBinder();
Receiver receiver = mReceivers.get(binder);
if (receiver == null) {
- receiver = new Receiver(listener, null, pid, uid, packageName, workSource,
+ receiver = new Receiver(listener, null, pid, uid, packageName, featureId, workSource,
hideFromAppOps, listenerIdentifier);
if (!receiver.linkToListenerDeathNotificationLocked(
receiver.getListener().asBinder())) {
@@ -2167,10 +2168,11 @@
@GuardedBy("mLock")
private Receiver getReceiverLocked(PendingIntent intent, int pid, int uid, String packageName,
- WorkSource workSource, boolean hideFromAppOps, @NonNull String listenerIdentifier) {
+ @Nullable String featureId, WorkSource workSource, boolean hideFromAppOps,
+ @NonNull String listenerIdentifier) {
Receiver receiver = mReceivers.get(intent);
if (receiver == null) {
- receiver = new Receiver(null, intent, pid, uid, packageName, workSource,
+ receiver = new Receiver(null, intent, pid, uid, packageName, featureId, workSource,
hideFromAppOps, listenerIdentifier);
mReceivers.put(intent, receiver);
}
@@ -2233,7 +2235,8 @@
@Override
public void requestLocationUpdates(LocationRequest request, ILocationListener listener,
- PendingIntent intent, String packageName, String listenerIdentifier) {
+ PendingIntent intent, String packageName, String featureId,
+ String listenerIdentifier) {
Preconditions.checkNotNull(listenerIdentifier);
synchronized (mLock) {
@@ -2289,11 +2292,11 @@
Receiver receiver;
if (intent != null) {
- receiver = getReceiverLocked(intent, pid, uid, packageName, workSource,
- hideFromAppOps, listenerIdentifier);
+ receiver = getReceiverLocked(intent, pid, uid, packageName, featureId,
+ workSource, hideFromAppOps, listenerIdentifier);
} else {
- receiver = getReceiverLocked(listener, pid, uid, packageName, workSource,
- hideFromAppOps, listenerIdentifier);
+ receiver = getReceiverLocked(listener, pid, uid, packageName, featureId,
+ workSource, hideFromAppOps, listenerIdentifier);
}
requestLocationUpdatesLocked(sanitizedRequest, receiver, uid, packageName);
} finally {
@@ -2362,9 +2365,10 @@
synchronized (mLock) {
Receiver receiver;
if (intent != null) {
- receiver = getReceiverLocked(intent, pid, uid, packageName, null, false, "");
+ receiver = getReceiverLocked(intent, pid, uid, packageName, null, null, false, "");
} else {
- receiver = getReceiverLocked(listener, pid, uid, packageName, null, false, "");
+ receiver = getReceiverLocked(listener, pid, uid, packageName, null, null, false,
+ "");
}
long identity = Binder.clearCallingIdentity();
@@ -2408,7 +2412,7 @@
}
@Override
- public Location getLastLocation(LocationRequest r, String packageName) {
+ public Location getLastLocation(LocationRequest r, String packageName, String featureId) {
synchronized (mLock) {
LocationRequest request = r != null ? r : DEFAULT_LOCATION_REQUEST;
int allowedResolutionLevel = getCallerAllowedResolutionLevel();
@@ -2483,8 +2487,8 @@
}
// Don't report location access if there is no last location to deliver.
if (lastLocation != null) {
- if (!reportLocationAccessNoThrow(pid, uid, packageName, allowedResolutionLevel,
- null)) {
+ if (!reportLocationAccessNoThrow(pid, uid, packageName, featureId,
+ allowedResolutionLevel, null)) {
if (D) {
Log.d(TAG, "not returning last loc for no op app: " + packageName);
}
@@ -2501,9 +2505,9 @@
@Override
public boolean getCurrentLocation(LocationRequest locationRequest,
ICancellationSignal remoteCancellationSignal, ILocationListener listener,
- String packageName, String listenerIdentifier) {
+ String packageName, String featureId, String listenerIdentifier) {
// side effect of validating locationRequest and packageName
- Location lastLocation = getLastLocation(locationRequest, packageName);
+ Location lastLocation = getLastLocation(locationRequest, packageName, featureId);
if (lastLocation != null) {
long locationAgeMs = TimeUnit.NANOSECONDS.toMillis(
SystemClock.elapsedRealtimeNanos() - lastLocation.getElapsedRealtimeNanos());
@@ -2538,7 +2542,8 @@
}
}
- requestLocationUpdates(locationRequest, listener, null, packageName, listenerIdentifier);
+ requestLocationUpdates(locationRequest, listener, null, packageName, featureId,
+ listenerIdentifier);
CancellationSignal cancellationSignal = CancellationSignal.fromTransport(
remoteCancellationSignal);
if (cancellationSignal != null) {
@@ -2588,7 +2593,7 @@
@Override
public void requestGeofence(LocationRequest request, Geofence geofence, PendingIntent intent,
- String packageName, String listenerIdentifier) {
+ String packageName, String featureId, String listenerIdentifier) {
Preconditions.checkNotNull(listenerIdentifier);
if (request == null) request = DEFAULT_LOCATION_REQUEST;
@@ -2636,7 +2641,7 @@
}
mGeofenceManager.addFence(sanitizedRequest, geofence, intent, allowedResolutionLevel,
- uid, packageName, listenerIdentifier);
+ uid, packageName, featureId, listenerIdentifier);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -2672,9 +2677,10 @@
}
@Override
- public boolean registerGnssStatusCallback(IGnssStatusListener listener, String packageName) {
+ public boolean registerGnssStatusCallback(IGnssStatusListener listener, String packageName,
+ String featureId) {
return mGnssManagerService == null ? false : mGnssManagerService.registerGnssStatusCallback(
- listener, packageName);
+ listener, packageName, featureId);
}
@Override
@@ -2684,12 +2690,12 @@
@Override
public boolean addGnssMeasurementsListener(IGnssMeasurementsListener listener,
- String packageName, String listenerIdentifier) {
+ String packageName, String featureId, String listenerIdentifier) {
Preconditions.checkNotNull(listenerIdentifier);
return mGnssManagerService == null ? false
- : mGnssManagerService.addGnssMeasurementsListener(listener, packageName,
- listenerIdentifier);
+ : mGnssManagerService.addGnssMeasurementsListener(listener, packageName, featureId,
+ listenerIdentifier);
}
@Override
@@ -2717,12 +2723,12 @@
@Override
public boolean addGnssNavigationMessageListener(IGnssNavigationMessageListener listener,
- String packageName, String listenerIdentifier) {
+ String packageName, String featureId, String listenerIdentifier) {
Preconditions.checkNotNull(listenerIdentifier);
return mGnssManagerService == null ? false
: mGnssManagerService.addGnssNavigationMessageListener(listener, packageName,
- listenerIdentifier);
+ featureId, listenerIdentifier);
}
@Override
@@ -3028,6 +3034,7 @@
receiver.mCallerIdentity.mPid,
receiver.mCallerIdentity.mUid,
receiver.mCallerIdentity.mPackageName,
+ receiver.mCallerIdentity.mFeatureId,
receiver.mAllowedResolutionLevel,
"Location sent to " + receiver.mCallerIdentity.mListenerIdentifier)) {
if (D) {
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 619c21e..44c81fc 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -988,18 +988,6 @@
}
}
- private String getErrorString(int modality, int error, int vendorCode) {
- for (AuthenticatorWrapper authenticator : mAuthenticators) {
- if (authenticator.modality == modality) {
- // TODO(b/141025588): Refactor IBiometricServiceReceiver.aidl#onError(...) to not
- // ask for a String error message, but derive it from the error code instead.
- return "";
- }
- }
- Slog.w(TAG, "Unable to get error string for modality: " + modality);
- return null;
- }
-
private void logDialogDismissed(int reason) {
if (reason == BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED) {
// Explicit auth, authentication confirmed.
diff --git a/services/core/java/com/android/server/location/CallerIdentity.java b/services/core/java/com/android/server/location/CallerIdentity.java
index 61e5d1f..75ba5b8 100644
--- a/services/core/java/com/android/server/location/CallerIdentity.java
+++ b/services/core/java/com/android/server/location/CallerIdentity.java
@@ -17,6 +17,7 @@
package com.android.server.location;
import android.annotation.NonNull;
+import android.annotation.Nullable;
/**
* Represents the calling process's uid, pid, and package name.
@@ -25,13 +26,15 @@
public final int mUid;
public final int mPid;
public final String mPackageName;
+ public final @Nullable String mFeatureId;
public final @NonNull String mListenerIdentifier;
- public CallerIdentity(int uid, int pid, String packageName,
+ public CallerIdentity(int uid, int pid, String packageName, @Nullable String featureId,
@NonNull String listenerIdentifier) {
mUid = uid;
mPid = pid;
mPackageName = packageName;
+ mFeatureId = featureId;
mListenerIdentifier = listenerIdentifier;
}
}
diff --git a/services/core/java/com/android/server/location/GeofenceManager.java b/services/core/java/com/android/server/location/GeofenceManager.java
index 4f10a71..17a2169 100644
--- a/services/core/java/com/android/server/location/GeofenceManager.java
+++ b/services/core/java/com/android/server/location/GeofenceManager.java
@@ -17,6 +17,7 @@
package com.android.server.location;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.content.ContentResolver;
@@ -152,7 +153,7 @@
}
public void addFence(LocationRequest request, Geofence geofence, PendingIntent intent,
- int allowedResolutionLevel, int uid, String packageName,
+ int allowedResolutionLevel, int uid, String packageName, @Nullable String featureId,
@NonNull String listenerIdentifier) {
if (D) {
Slog.d(TAG, "addFence: request=" + request + ", geofence=" + geofence
@@ -160,8 +161,8 @@
}
GeofenceState state = new GeofenceState(geofence,
- request.getExpireAt(), allowedResolutionLevel, uid, packageName, listenerIdentifier,
- intent);
+ request.getExpireAt(), allowedResolutionLevel, uid, packageName, featureId,
+ listenerIdentifier, intent);
synchronized (mLock) {
// first make sure it doesn't already exist
for (int i = mFences.size() - 1; i >= 0; i--) {
@@ -304,7 +305,7 @@
int op = LocationManagerService.resolutionLevelToOp(state.mAllowedResolutionLevel);
if (op >= 0) {
if (mAppOps.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, state.mUid,
- state.mPackageName, null, state.mListenerIdentifier)
+ state.mPackageName, state.mFeatureId, state.mListenerIdentifier)
!= AppOpsManager.MODE_ALLOWED) {
if (D) {
Slog.d(TAG, "skipping geofence processing for no op app: "
diff --git a/services/core/java/com/android/server/location/GeofenceState.java b/services/core/java/com/android/server/location/GeofenceState.java
index fe0719d..a91a1dc 100644
--- a/services/core/java/com/android/server/location/GeofenceState.java
+++ b/services/core/java/com/android/server/location/GeofenceState.java
@@ -18,6 +18,7 @@
package com.android.server.location;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.PendingIntent;
import android.location.Geofence;
import android.location.Location;
@@ -39,6 +40,7 @@
public final int mAllowedResolutionLevel;
public final int mUid;
public final String mPackageName;
+ public final @Nullable String mFeatureId;
public final @NonNull String mListenerIdentifier;
public final PendingIntent mIntent;
@@ -46,7 +48,8 @@
double mDistanceToCenter; // current distance to center of fence
public GeofenceState(Geofence fence, long expireAt, int allowedResolutionLevel, int uid,
- String packageName, @NonNull String listenerIdentifier, PendingIntent intent) {
+ String packageName, @Nullable String featureId, @NonNull String listenerIdentifier,
+ PendingIntent intent) {
mState = STATE_UNKNOWN;
mDistanceToCenter = Double.MAX_VALUE;
@@ -55,6 +58,7 @@
mAllowedResolutionLevel = allowedResolutionLevel;
mUid = uid;
mPackageName = packageName;
+ mFeatureId = featureId;
mListenerIdentifier = listenerIdentifier;
mIntent = intent;
diff --git a/services/core/java/com/android/server/location/RemoteListenerHelper.java b/services/core/java/com/android/server/location/RemoteListenerHelper.java
index 25b544f..60ce1f4 100644
--- a/services/core/java/com/android/server/location/RemoteListenerHelper.java
+++ b/services/core/java/com/android/server/location/RemoteListenerHelper.java
@@ -182,7 +182,7 @@
}
return mAppOps.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, callerIdentity.mUid,
- callerIdentity.mPackageName, null,
+ callerIdentity.mPackageName, callerIdentity.mFeatureId,
"Location sent to " + callerIdentity.mListenerIdentifier)
== AppOpsManager.MODE_ALLOWED;
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 00c0566..ed2bb3d5 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -259,7 +259,7 @@
// Don't hold mSessions lock when calling restoreSession, since it might trigger an APK
// atomic install which needs to query sessions, which requires lock on mSessions.
for (PackageInstallerSession session : stagedSessionsToRestore) {
- if (mPm.isDeviceUpgrading()) {
+ if (mPm.isDeviceUpgrading() && !session.isStagedAndInTerminalState()) {
session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
"Build fingerprint has changed");
}
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 23df1c5..5783932 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -146,17 +146,16 @@
}
// During system reboot, add a DEVICE_SHUTDOWN event to the end of event list, the timestamp
- // is last time UsageStatsDatabase is persisted to disk.
+ // is last time UsageStatsDatabase is persisted to disk or the last event's time whichever
+ // is higher (because the file system timestamp is round down to integral seconds).
// Also add a DEVICE_STARTUP event with current system timestamp.
final IntervalStats currentDailyStats = mCurrentStats[INTERVAL_DAILY];
if (currentDailyStats != null) {
- // File system timestamp only has precision of 1 second, add 1000ms to make up
- // for the loss of round up.
- final Event shutdownEvent =
- new Event(DEVICE_SHUTDOWN, currentDailyStats.lastTimeSaved + 1000);
+ final Event shutdownEvent = new Event(DEVICE_SHUTDOWN,
+ Math.max(currentDailyStats.lastTimeSaved, currentDailyStats.endTime));
shutdownEvent.mPackage = Event.DEVICE_EVENT_PACKAGE_NAME;
currentDailyStats.addEvent(shutdownEvent);
- final Event startupEvent = new Event(DEVICE_STARTUP, currentTimeMillis);
+ final Event startupEvent = new Event(DEVICE_STARTUP, System.currentTimeMillis());
startupEvent.mPackage = Event.DEVICE_EVENT_PACKAGE_NAME;
currentDailyStats.addEvent(startupEvent);
}
diff --git a/startop/apps/test/Android.bp b/startop/apps/test/Android.bp
index 2ff26b8..5de7fd2 100644
--- a/startop/apps/test/Android.bp
+++ b/startop/apps/test/Android.bp
@@ -18,7 +18,8 @@
name: "startop_test_app",
srcs: [
"src/ComplexLayoutInflationActivity.java",
- "src/CPUIntensive.java",
+ "src/CPUIntensiveBenchmarkActivity.java",
+ "src/CPUIntensiveBenchmarks.java",
"src/EmptyActivity.java",
"src/FrameLayoutInflationActivity.java",
"src/LayoutInflationActivity.java",
diff --git a/startop/apps/test/AndroidManifest.xml b/startop/apps/test/AndroidManifest.xml
index ebe2584..b08072e 100644
--- a/startop/apps/test/AndroidManifest.xml
+++ b/startop/apps/test/AndroidManifest.xml
@@ -37,6 +37,18 @@
</activity>
<activity
+ android:label="CPU Intensive Benchmark Test"
+ android:name=".CPUIntensiveBenchmarkActivity"
+ android:exported="true" >
+
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <activity
android:label="Empty Activity Layout Test"
android:name=".EmptyActivity"
android:exported="true" >
diff --git a/startop/apps/test/src/CPUIntensive.java b/startop/apps/test/src/CPUIntensive.java
deleted file mode 100644
index a411e8c..0000000
--- a/startop/apps/test/src/CPUIntensive.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2019 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.
- */
-
-/**
- * A threaded CPU intensive class for use in benchmarks.
- */
-
-package com.android.startop.test;
-
-final class CPUIntensive {
- public static final int THREAD_COUNT = 8;
- public static final int ARRAY_SIZE = 30000;
- public static int[][] array = new int[THREAD_COUNT][ARRAY_SIZE];
-
- static class WorkerThread extends Thread {
- int mThreadNumber;
- WorkerThread(int number) {
- mThreadNumber = number;
- }
- public void run() {
- final int arrayLength = array[mThreadNumber].length;
- for (int i = 0; i < arrayLength; ++i) {
- array[mThreadNumber][i] = i * i;
- }
- for (int i = 0; i < arrayLength; ++i) {
- for (int j = 0; j < arrayLength; ++j) {
- int swap = array[mThreadNumber][j];
- array[mThreadNumber][j] = array[mThreadNumber][(j + i) % arrayLength];
- array[mThreadNumber][(j + i) % arrayLength] = swap;
- }
- }
- }
- };
-
- public static void doSomeWork(int threadCount) {
- WorkerThread[] threads = new WorkerThread[threadCount];
- // Create the threads.
- for (int i = 0; i < threadCount; ++i) {
- threads[i] = new WorkerThread(i);
- }
- // Start the threads.
- for (int i = 0; i < threadCount; ++i) {
- threads[i].start();
- }
- // Join the threads.
- for (int i = 0; i < threadCount; ++i) {
- try {
- threads[i].join();
- } catch (Exception ex) {
- }
- }
- }
-}
-
diff --git a/startop/apps/test/src/CPUIntensiveBenchmarkActivity.java b/startop/apps/test/src/CPUIntensiveBenchmarkActivity.java
new file mode 100644
index 0000000..2ec5308
--- /dev/null
+++ b/startop/apps/test/src/CPUIntensiveBenchmarkActivity.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 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 com.android.startop.test;
+
+import android.os.Bundle;
+
+public class CPUIntensiveBenchmarkActivity extends SystemServerBenchmarkActivity {
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.system_server_benchmark_page);
+
+ mBenchmarkList = findViewById(R.id.benchmark_list);
+
+ CPUIntensiveBenchmarks.initializeBenchmarks(this, this);
+ }
+}
diff --git a/startop/apps/test/src/CPUIntensiveBenchmarks.java b/startop/apps/test/src/CPUIntensiveBenchmarks.java
new file mode 100644
index 0000000..19d0b63
--- /dev/null
+++ b/startop/apps/test/src/CPUIntensiveBenchmarks.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+/**
+ * A threaded CPU intensive class for use in benchmarks.
+ */
+
+package com.android.startop.test;
+
+import android.app.Activity;
+
+public class CPUIntensiveBenchmarks {
+ public static final int ARRAY_SIZE = 30000;
+ public static int[][] mArray;
+
+ static class WorkerThread extends Thread {
+ int mThreadNumber;
+ WorkerThread(int number) {
+ mThreadNumber = number;
+ }
+ public void run() {
+ final int arrayLength = mArray[mThreadNumber].length;
+ for (int i = 0; i < arrayLength; ++i) {
+ mArray[mThreadNumber][i] = i * i;
+ }
+ for (int i = 0; i < arrayLength; ++i) {
+ for (int j = 0; j < arrayLength; ++j) {
+ int swap = mArray[mThreadNumber][j];
+ mArray[mThreadNumber][j] = mArray[mThreadNumber][(j + i) % arrayLength];
+ mArray[mThreadNumber][(j + i) % arrayLength] = swap;
+ }
+ }
+ }
+ };
+
+ static void doSomeWork(int threadCount) {
+ mArray = new int[threadCount][ARRAY_SIZE];
+ WorkerThread[] threads = new WorkerThread[threadCount];
+ // Create the threads.
+ for (int i = 0; i < threadCount; ++i) {
+ threads[i] = new WorkerThread(i);
+ }
+ // Start the threads.
+ for (int i = 0; i < threadCount; ++i) {
+ threads[i].start();
+ }
+ // Join the threads.
+ for (int i = 0; i < threadCount; ++i) {
+ try {
+ threads[i].join();
+ } catch (Exception ex) {
+ }
+ }
+ }
+
+ // Time limit to run benchmarks in seconds
+ public static final int TIME_LIMIT = 5;
+
+ static void initializeBenchmarks(Activity parent, BenchmarkRunner benchmarks) {
+ benchmarks.addBenchmark("Use 1 thread", () -> {
+ doSomeWork(1);
+ });
+ benchmarks.addBenchmark("Use 2 threads", () -> {
+ doSomeWork(2);
+ });
+ benchmarks.addBenchmark("Use 4 threads", () -> {
+ doSomeWork(4);
+ });
+ benchmarks.addBenchmark("Use 8 threads", () -> {
+ doSomeWork(8);
+ });
+ benchmarks.addBenchmark("Use 16 threads", () -> {
+ doSomeWork(16);
+ });
+ }
+}
diff --git a/startop/apps/test/src/SystemServerBenchmarkActivity.java b/startop/apps/test/src/SystemServerBenchmarkActivity.java
index 75ea69b..6be8df3 100644
--- a/startop/apps/test/src/SystemServerBenchmarkActivity.java
+++ b/startop/apps/test/src/SystemServerBenchmarkActivity.java
@@ -17,28 +17,20 @@
package com.android.startop.test;
import android.app.Activity;
-import android.app.ActivityManager;
-import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.AsyncTask;
import android.os.Bundle;
-import android.view.ViewGroup;
import android.widget.Button;
import android.widget.GridLayout;
import android.widget.TextView;
public class SystemServerBenchmarkActivity extends Activity implements BenchmarkRunner {
- private GridLayout benchmarkList;
+ protected GridLayout mBenchmarkList;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.system_server_benchmark_page);
- benchmarkList = findViewById(R.id.benchmark_list);
+ mBenchmarkList = findViewById(R.id.benchmark_list);
SystemServerBenchmarks.initializeBenchmarks(this, this);
}
@@ -49,7 +41,7 @@
* @param name A short name that shows up in the UI or benchmark results
*/
public void addBenchmark(CharSequence name, Runnable thunk) {
- Context context = benchmarkList.getContext();
+ Context context = mBenchmarkList.getContext();
Button button = new Button(context);
TextView mean = new TextView(context);
TextView stdev = new TextView(context);
@@ -68,8 +60,8 @@
});
});
- benchmarkList.addView(button);
- benchmarkList.addView(mean);
- benchmarkList.addView(stdev);
+ mBenchmarkList.addView(button);
+ mBenchmarkList.addView(mean);
+ mBenchmarkList.addView(stdev);
}
}
diff --git a/startop/apps/test/src/SystemServerBenchmarks.java b/startop/apps/test/src/SystemServerBenchmarks.java
index 5918503..25b43f4 100644
--- a/startop/apps/test/src/SystemServerBenchmarks.java
+++ b/startop/apps/test/src/SystemServerBenchmarks.java
@@ -60,22 +60,6 @@
benchmarks.addBenchmark("Empty", () -> {
});
- benchmarks.addBenchmark("CPU Intensive (1 thread)", () -> {
- CPUIntensive.doSomeWork(1);
- });
-
- benchmarks.addBenchmark("CPU Intensive (2 thread)", () -> {
- CPUIntensive.doSomeWork(2);
- });
-
- benchmarks.addBenchmark("CPU Intensive (4 thread)", () -> {
- CPUIntensive.doSomeWork(4);
- });
-
- benchmarks.addBenchmark("CPU Intensive (8 thread)", () -> {
- CPUIntensive.doSomeWork(8);
- });
-
PackageManager pm = parent.getPackageManager();
benchmarks.addBenchmark("getInstalledApplications", () -> {
pm.getInstalledApplications(PackageManager.MATCH_SYSTEM_ONLY);
diff --git a/wifi/java/android/net/wifi/hotspot2/ConfigParser.java b/wifi/java/android/net/wifi/hotspot2/ConfigParser.java
index e8e8731..bb01365 100644
--- a/wifi/java/android/net/wifi/hotspot2/ConfigParser.java
+++ b/wifi/java/android/net/wifi/hotspot2/ConfigParser.java
@@ -182,6 +182,12 @@
throw new IOException("Passpoint profile missing credential");
}
+ // Don't allow the installer to make changes to the update identifier. This is an
+ // indicator of an R2 (or newer) network.
+ if (config.getUpdateIdentifier() != Integer.MIN_VALUE) {
+ config.setUpdateIdentifier(Integer.MIN_VALUE);
+ }
+
// Parse CA (Certificate Authority) certificate.
byte[] caCertData = mimeParts.get(TYPE_CA_CERT);
if (caCertData != null) {
diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
index 557b7c9..e9aa076 100644
--- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
+++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
@@ -65,6 +65,7 @@
* Configurations under HomeSp subtree.
*/
private HomeSp mHomeSp = null;
+
/**
* Set the Home SP (Service Provider) information.
*
@@ -248,7 +249,10 @@
mSubscriptionExpirationTimeInMillis = subscriptionExpirationTimeInMillis;
}
/**
- * @hide
+ * Utility method to get the time this subscription will expire. It is in the format of number
+ * of milliseconds since January 1, 1970, 00:00:00 GMT.
+ *
+ * @return The time this subscription will expire, or Long.MIN_VALUE to indicate unset value
*/
public long getSubscriptionExpirationTimeInMillis() {
return mSubscriptionExpirationTimeInMillis;
@@ -521,6 +525,8 @@
.append("\n");
builder.append("UsageLimitDataLimit: ").append(mUsageLimitDataLimit).append("\n");
builder.append("UsageLimitTimeLimit: ").append(mUsageLimitTimeLimitInMinutes).append("\n");
+ builder.append("Provisioned by a subscription server: ")
+ .append(isOsuProvisioned() ? "Yes" : "No").append("\n");
if (mHomeSp != null) {
builder.append("HomeSP Begin ---\n");
builder.append(mHomeSp);
@@ -728,4 +734,14 @@
}
return true;
}
+
+ /**
+ * Indicates if the Passpoint Configuration was provisioned by a subscription (OSU) server,
+ * which means that it's an R2 (or R3) profile.
+ *
+ * @return true if the Passpoint Configuration was provisioned by a subscription server.
+ */
+ public boolean isOsuProvisioned() {
+ return getUpdateIdentifier() != Integer.MIN_VALUE;
+ }
}
diff --git a/wifi/tests/assets/hsr1/HSR1ProfileWithUpdateIdentifier.base64 b/wifi/tests/assets/hsr1/HSR1ProfileWithUpdateIdentifier.base64
new file mode 100644
index 0000000..f4d2abb
--- /dev/null
+++ b/wifi/tests/assets/hsr1/HSR1ProfileWithUpdateIdentifier.base64
@@ -0,0 +1,88 @@
+TUlNRS1WZXJzaW9uOiAxLjAKQ29udGVudC1UeXBlOiBtdWx0aXBhcnQvbWl4ZWQ7IGJvdW5kYXJ5
+PXtib3VuZGFyeX07IGNoYXJzZXQ9VVRGLTgKQ29udGVudC1UcmFuc2Zlci1FbmNvZGluZzogYmFz
+ZTY0CgotLXtib3VuZGFyeX0KQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi94LXBhc3Nwb2ludC1w
+cm9maWxlOyBjaGFyc2V0PVVURi04CkNvbnRlbnQtVHJhbnNmZXItRW5jb2Rpbmc6IGJhc2U2NAoK
+UEUxbmJYUlVjbVZsSUhodGJHNXpQU0p6ZVc1amJXdzZaRzFrWkdZeExqSWlQZ29nSUR4V1pYSkVW
+RVErTVM0eVBDOVdaWEpFVkVRKwpDaUFnUEU1dlpHVStDaUFnSUNBOFRtOWtaVTVoYldVK1VHVnlV
+SEp2ZG1sa1pYSlRkV0p6WTNKcGNIUnBiMjQ4TDA1dlpHVk9ZVzFsClBnb2dJQ0FnUEZKVVVISnZj
+R1Z5ZEdsbGN6NEtJQ0FnSUNBZ1BGUjVjR1UrQ2lBZ0lDQWdJQ0FnUEVSRVJrNWhiV1UrZFhKdU9u
+ZG0KWVRwdGJ6cG9iM1J6Y0c5ME1tUnZkREF0Y0dWeWNISnZkbWxrWlhKemRXSnpZM0pwY0hScGIy
+NDZNUzR3UEM5RVJFWk9ZVzFsUGdvZwpJQ0FnSUNBOEwxUjVjR1UrQ2lBZ0lDQThMMUpVVUhKdmNH
+VnlkR2xsY3o0S0lDQWdJRHhPYjJSbFBnb2dJQ0FnSUNBOFRtOWtaVTVoCmJXVStWWEJrWVhSbFNX
+UmxiblJwWm1sbGNqd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lEeFdZV3gxWlQ0eE1qTTBQQzlXWVd4
+MVpUNEsKSUNBZ0lEd3ZUbTlrWlQ0S0lDQWdJRHhPYjJSbFBnb2dJQ0FnSUNBOFRtOWtaVTVoYldV
+K2FUQXdNVHd2VG05a1pVNWhiV1UrQ2lBZwpJQ0FnSUR4T2IyUmxQZ29nSUNBZ0lDQWdJRHhPYjJS
+bFRtRnRaVDVJYjIxbFUxQThMMDV2WkdWT1lXMWxQZ29nSUNBZ0lDQWdJRHhPCmIyUmxQZ29nSUNB
+Z0lDQWdJQ0FnUEU1dlpHVk9ZVzFsUGtaeWFXVnVaR3g1VG1GdFpUd3ZUbTlrWlU1aGJXVStDaUFn
+SUNBZ0lDQWcKSUNBOFZtRnNkV1UrUTJWdWRIVnllU0JJYjNWelpUd3ZWbUZzZFdVK0NpQWdJQ0Fn
+SUNBZ1BDOU9iMlJsUGdvZ0lDQWdJQ0FnSUR4TwpiMlJsUGdvZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZP
+WVcxbFBrWlJSRTQ4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ1BGWmhiSFZsClBtMXBOaTVq
+Ynk1MWF6d3ZWbUZzZFdVK0NpQWdJQ0FnSUNBZ1BDOU9iMlJsUGdvZ0lDQWdJQ0FnSUR4T2IyUmxQ
+Z29nSUNBZ0lDQWcKSUNBZ1BFNXZaR1ZPWVcxbFBsSnZZVzFwYm1kRGIyNXpiM0owYVhWdFQwazhM
+MDV2WkdWT1lXMWxQZ29nSUNBZ0lDQWdJQ0FnUEZaaApiSFZsUGpFeE1qSXpNeXcwTkRVMU5qWThM
+MVpoYkhWbFBnb2dJQ0FnSUNBZ0lEd3ZUbTlrWlQ0S0lDQWdJQ0FnUEM5T2IyUmxQZ29nCklDQWdJ
+Q0E4VG05a1pUNEtJQ0FnSUNBZ0lDQThUbTlrWlU1aGJXVStRM0psWkdWdWRHbGhiRHd2VG05a1pV
+NWhiV1UrQ2lBZ0lDQWcKSUNBZ1BFNXZaR1UrQ2lBZ0lDQWdJQ0FnSUNBOFRtOWtaVTVoYldVK1Vt
+VmhiRzA4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZwpQRlpoYkhWbFBuTm9ZV3RsYmk1emRH
+bHljbVZrTG1OdmJUd3ZWbUZzZFdVK0NpQWdJQ0FnSUNBZ1BDOU9iMlJsUGdvZ0lDQWdJQ0FnCklE
+eE9iMlJsUGdvZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBsVnpaWEp1WVcxbFVHRnpjM2R2Y21R
+OEwwNXZaR1ZPWVcxbFBnb2cKSUNBZ0lDQWdJQ0FnUEU1dlpHVStDaUFnSUNBZ0lDQWdJQ0FnSUR4
+T2IyUmxUbUZ0WlQ1VmMyVnlibUZ0WlR3dlRtOWtaVTVoYldVKwpDaUFnSUNBZ0lDQWdJQ0FnSUR4
+V1lXeDFaVDVxWVcxbGN6d3ZWbUZzZFdVK0NpQWdJQ0FnSUNBZ0lDQThMMDV2WkdVK0NpQWdJQ0Fn
+CklDQWdJQ0E4VG05a1pUNEtJQ0FnSUNBZ0lDQWdJQ0FnUEU1dlpHVk9ZVzFsUGxCaGMzTjNiM0pr
+UEM5T2IyUmxUbUZ0WlQ0S0lDQWcKSUNBZ0lDQWdJQ0FnUEZaaGJIVmxQbGx0T1hWYVJFRjNUbmM5
+UFR3dlZtRnNkV1UrQ2lBZ0lDQWdJQ0FnSUNBOEwwNXZaR1UrQ2lBZwpJQ0FnSUNBZ0lDQThUbTlr
+WlQ0S0lDQWdJQ0FnSUNBZ0lDQWdQRTV2WkdWT1lXMWxQa1ZCVUUxbGRHaHZaRHd2VG05a1pVNWhi
+V1UrCkNpQWdJQ0FnSUNBZ0lDQWdJRHhPYjJSbFBnb2dJQ0FnSUNBZ0lDQWdJQ0FnSUR4T2IyUmxU
+bUZ0WlQ1RlFWQlVlWEJsUEM5T2IyUmwKVG1GdFpUNEtJQ0FnSUNBZ0lDQWdJQ0FnSUNBOFZtRnNk
+V1UrTWpFOEwxWmhiSFZsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThMMDV2WkdVKwpDaUFnSUNBZ0lDQWdJ
+Q0FnSUR4T2IyUmxQZ29nSUNBZ0lDQWdJQ0FnSUNBZ0lEeE9iMlJsVG1GdFpUNUpibTVsY2sxbGRH
+aHZaRHd2ClRtOWtaVTVoYldVK0NpQWdJQ0FnSUNBZ0lDQWdJQ0FnUEZaaGJIVmxQazFUTFVOSVFW
+QXRWakk4TDFaaGJIVmxQZ29nSUNBZ0lDQWcKSUNBZ0lDQThMMDV2WkdVK0NpQWdJQ0FnSUNBZ0lD
+QThMMDV2WkdVK0NpQWdJQ0FnSUNBZ1BDOU9iMlJsUGdvZ0lDQWdJQ0FnSUR4TwpiMlJsUGdvZ0lD
+QWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBrUnBaMmwwWVd4RFpYSjBhV1pwWTJGMFpUd3ZUbTlrWlU1
+aGJXVStDaUFnCklDQWdJQ0FnSUNBOFRtOWtaVDRLSUNBZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcx
+bFBrTmxjblJwWm1sallYUmxWSGx3WlR3dlRtOWsKWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0FnSUR4
+V1lXeDFaVDU0TlRBNWRqTThMMVpoYkhWbFBnb2dJQ0FnSUNBZ0lDQWdQQzlPYjJSbApQZ29nSUNB
+Z0lDQWdJQ0FnUEU1dlpHVStDaUFnSUNBZ0lDQWdJQ0FnSUR4T2IyUmxUbUZ0WlQ1RFpYSjBVMGhC
+TWpVMlJtbHVaMlZ5CmNISnBiblE4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZz
+ZFdVK01XWXhaakZtTVdZeFpqRm1NV1l4WmpGbU1XWXgKWmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4
+WmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4WmpGbU1XWXhaand2Vm1Gc2RXVStDaUFnSUNBZwpJQ0Fn
+SUNBOEwwNXZaR1UrQ2lBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lDQWdJRHhPYjJSbFBnb2dJ
+Q0FnSUNBZ0lDQWdQRTV2ClpHVk9ZVzFsUGxOSlRUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJ
+Q0E4VG05a1pUNEtJQ0FnSUNBZ0lDQWdJQ0FnUEU1dlpHVk8KWVcxbFBrbE5VMGs4TDA1dlpHVk9Z
+VzFsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZzZFdVK2FXMXphVHd2Vm1Gc2RXVStDaUFnSUNBZwpJ
+Q0FnSUNBOEwwNXZaR1UrQ2lBZ0lDQWdJQ0FnSUNBOFRtOWtaVDRLSUNBZ0lDQWdJQ0FnSUNBZ1BF
+NXZaR1ZPWVcxbFBrVkJVRlI1CmNHVThMMDV2WkdWT1lXMWxQZ29nSUNBZ0lDQWdJQ0FnSUNBOFZt
+RnNkV1UrTWpROEwxWmhiSFZsUGdvZ0lDQWdJQ0FnSUNBZ1BDOU8KYjJSbFBnb2dJQ0FnSUNBZ0lE
+d3ZUbTlrWlQ0S0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ1BDOU9iMlJsUGdvZ0lEd3ZUbTlrWlQ0
+SwpQQzlOWjIxMFZISmxaVDRLCgotLXtib3VuZGFyeX0KQ29udGVudC1UeXBlOiBhcHBsaWNhdGlv
+bi94LXg1MDktY2EtY2VydApDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiYXNlNjQKCkxTMHRM
+UzFDUlVkSlRpQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVVJMUkVORFFXaERaMEYzU1VKQlow
+bEtRVWxNYkVaa2QzcE0KVm5WeVRVRXdSME5UY1VkVFNXSXpSRkZGUWtOM1ZVRk5Ra2w0UlVSQlQw
+Sm5UbFlLUWtGTlZFSXdWa0pWUTBKRVVWUkZkMGhvWTA1TgpWRmwzVFZSRmVVMVVSVEZOUkVVeFYy
+aGpUazFxV1hkTlZFRTFUVlJGTVUxRVJURlhha0ZUVFZKQmR3cEVaMWxFVmxGUlJFVjNaRVpSClZr
+Rm5VVEJGZUUxSlNVSkpha0ZPUW1kcmNXaHJhVWM1ZHpCQ1FWRkZSa0ZCVDBOQlVUaEJUVWxKUWtO
+blMwTkJVVVZCQ25wdVFWQlYKZWpJMlRYTmhaVFIzY3pRelkzcFNOREV2U2pKUmRISlRTVnBWUzIx
+V1ZYTldkVzFFWWxsSWNsQk9kbFJZUzFOTldFRmpaWGRQVWtSUgpXVmdLVW5GMlNIWndiamhEYzJO
+Q01TdHZSMWhhZGtoM2VHbzBlbFl3VjB0dlN6SjZaVmhyWVhVemRtTjViRE5JU1V0MWNFcG1jVEpV
+ClJVRkRaV1pXYW1vd2RBcEtWeXRZTXpWUVIxZHdPUzlJTlhwSlZVNVdUbFpxVXpkVmJYTTRORWwy
+UzJoU1FqZzFNVEpRUWpsVmVVaGgKWjFoWlZsZzFSMWR3UVdOV2NIbG1jbXhTQ2taSk9WRmthR2dy
+VUdKck1IVjVhM1JrWW1ZdlEyUm1aMGhQYjJWaWNsUjBkMUpzYWswdwpiMFIwV0NzeVEzWTJhakIz
+UWtzM2FFUTRjRkIyWmpFcmRYa0tSM3BqZW1sblFWVXZORXQzTjJWYWNYbGtaamxDS3pWU2RYQlNL
+MGxhCmFYQllOREY0UldsSmNrdFNkM0ZwTlRFM1YxZDZXR05xWVVjeVkwNWlaalExTVFwNGNFZzFV
+RzVXTTJreGRIRXdOR3BOUjFGVmVrWjMKU1VSQlVVRkNielJIUVUxSU5IZElVVmxFVmxJd1QwSkNX
+VVZHU1hkWU5IWnpPRUpwUW1OVFkyOWtDalZ1YjFwSVVrMDRSVFFyYVUxRgpTVWRCTVZWa1NYZFJO
+MDFFYlVGR1NYZFlOSFp6T0VKcFFtTlRZMjlrTlc1dldraFNUVGhGTkN0cGIxSmhhMFpFUVZNS1RW
+SkJkMFJuCldVUldVVkZFUlhka1JsRldRV2RSTUVWNFoyZHJRV2QxVlZZelJFMTBWelp6ZDBSQldV
+UldVakJVUWtGVmQwRjNSVUl2ZWtGTVFtZE8KVmdwSVVUaEZRa0ZOUTBGUldYZEVVVmxLUzI5YVNX
+aDJZMDVCVVVWTVFsRkJSR2RuUlVKQlJtWlJjVTlVUVRkU2RqZExLMngxVVRkdwpibUZ6TkVKWmQw
+aEZDamxIUlZBdmRXOW9kalpMVDNrd1ZFZFJSbUp5VWxScVJtOU1WazVDT1VKYU1YbHRUVVJhTUM5
+VVNYZEpWV00zCmQyazNZVGgwTlcxRmNWbElNVFV6ZDFjS1lWZHZiMmxUYW5sTVRHaDFTVFJ6VG5K
+T1EwOTBhWE5rUW5FeWNqSk5SbGgwTm1nd2JVRlIKV1U5UWRqaFNPRXMzTDJablUzaEhSbkY2YUhs
+T2JXMVdUQW94Y1VKS2JHUjRNelJUY0hkelZFRk1VVlpRWWpSb1IzZEtlbHBtY2pGUQpZM0JGVVhn
+MmVFMXVWR3c0ZUVWWFdrVXpUWE01T1hWaFZYaGlVWEZKZDFKMUNreG5RVTlyVGtOdFdUSnRPRGxX
+YUhwaFNFb3hkVlk0Ck5VRmtUUzkwUkN0WmMyMXNibTVxZERsTVVrTmxhbUpDYVhCcVNVZHFUMWh5
+WnpGS1VDdHNlRllLYlhWTk5IWklLMUF2Yld4dGVITlEKVUhvd1pEWTFZaXRGUjIxS1duQnZUR3RQ
+TDNSa1RrNTJRMWw2YWtwd1ZFVlhjRVZ6VHpaT1RXaExXVzg5Q2kwdExTMHRSVTVFSUVORgpVbFJK
+UmtsRFFWUkZMUzB0TFMwSwotLXtib3VuZGFyeX0tLQo=
\ No newline at end of file
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/ConfigParserTest.java b/wifi/tests/src/android/net/wifi/hotspot2/ConfigParserTest.java
index d9a1d9af..c815d75 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/ConfigParserTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/ConfigParserTest.java
@@ -54,6 +54,8 @@
"assets/hsr1/HSR1ProfileWithInvalidContentType.base64";
private static final String PASSPOINT_INSTALLATION_FILE_WITHOUT_PROFILE =
"assets/hsr1/HSR1ProfileWithoutProfile.base64";
+ private static final String PASSPOINT_INSTALLATION_FILE_WITH_UPDATE_ID =
+ "assets/hsr1/HSR1ProfileWithUpdateIdentifier.base64";
/**
* Read the content of the given resource file into a String.
@@ -201,4 +203,22 @@
assertNull(ConfigParser.parsePasspointConfig(
"application/x-wifi-config", configStr.getBytes()));
}
+
+ /**
+ * Verify a valid installation file is parsed successfully with the matching contents, and that
+ * Update identifier is cleared.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void parseConfigFileWithUpdateIdentifier() throws Exception {
+ String configStr = loadResourceFile(PASSPOINT_INSTALLATION_FILE_WITH_UPDATE_ID);
+ PasspointConfiguration expectedConfig = generateConfigurationFromProfile();
+ PasspointConfiguration actualConfig =
+ ConfigParser.parsePasspointConfig(
+ "application/x-wifi-config", configStr.getBytes());
+ // Expected configuration does not contain an update identifier
+ assertTrue(actualConfig.equals(expectedConfig));
+ }
+
}
\ No newline at end of file
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
index c3b074e..f501b16 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
@@ -183,7 +183,7 @@
PasspointConfiguration config = PasspointTestUtils.createConfig();
assertTrue(config.validate());
- assertTrue(config.validateForR2());
+ assertFalse(config.isOsuProvisioned());
}
/**
@@ -241,7 +241,6 @@
config.setPolicy(null);
assertTrue(config.validate());
- assertTrue(config.validateForR2());
}
/**
@@ -271,7 +270,6 @@
config.setAaaServerTrustedNames(null);
assertTrue(config.validate());
- assertTrue(config.validateForR2());
}
/**
@@ -348,4 +346,17 @@
PasspointConfiguration copyConfig = new PasspointConfiguration(sourceConfig);
assertTrue(copyConfig.equals(sourceConfig));
}
+
+ /**
+ * Verify that a configuration containing all fields is valid for R2.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateFullR2Config() throws Exception {
+ PasspointConfiguration config = PasspointTestUtils.createR2Config();
+ assertTrue(config.validate());
+ assertTrue(config.validateForR2());
+ assertTrue(config.isOsuProvisioned());
+ }
}
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointTestUtils.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointTestUtils.java
index adf74eb..8d55acb 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointTestUtils.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointTestUtils.java
@@ -132,7 +132,6 @@
*/
public static PasspointConfiguration createConfig() {
PasspointConfiguration config = new PasspointConfiguration();
- config.setUpdateIdentifier(1234);
config.setHomeSp(createHomeSp());
config.setAaaServerTrustedNames(
new String[] {"trusted.fqdn.com", "another-trusted.fqdn.com"});
@@ -145,7 +144,6 @@
trustRootCertList.put("trustRoot.cert2.com",
new byte[CERTIFICATE_FINGERPRINT_BYTES]);
config.setTrustRootCertList(trustRootCertList);
- config.setUpdateIdentifier(1);
config.setCredentialPriority(120);
config.setSubscriptionCreationTimeInMillis(231200);
config.setSubscriptionExpirationTimeInMillis(2134232);
@@ -160,4 +158,15 @@
config.setServiceFriendlyNames(friendlyNames);
return config;
}
+
+ /**
+ * Helper function for creating an R2 {@link PasspointConfiguration} for testing.
+ *
+ * @return {@link PasspointConfiguration}
+ */
+ public static PasspointConfiguration createR2Config() {
+ PasspointConfiguration config = createConfig();
+ config.setUpdateIdentifier(1234);
+ return config;
+ }
}