From a0961aaf560f3fa9e0b5038b639dcd00ec7765a6 Mon Sep 17 00:00:00 2001 From: Kieron Quinn Date: Wed, 29 Jun 2022 20:52:35 +0100 Subject: [PATCH] - Added the ability for On Demand recognitions to run without the Magisk overlay module, **so long as the device is rooted**. You must start Shizuku as root or use Sui to make use of this method, and the overlay is still preferred if it works on your device. Please read the [Wiki page](https://github.com/KieronQuinn/AmbientMusicMod/wiki/Enabling-On-Demand) for more information. - Further improvements to the On Demand overlay, fixing issues on OnePlus devices - Fixed the Lock Screen overlay not appearing when Smart Lock is enabled - Fixed Alternative Encoding and Lock Screen Overlay colour options not being backed up --- app/build.gradle | 4 +- .../app/ambientmusicmod/IShellProxy.aidl | 10 +- .../RootMusicRecognitionManager.kt | 394 ++++++++++++++++++ .../model/backup/SettingsBackup.kt | 9 +- .../repositories/BackupRestoreRepository.kt | 9 +- .../repositories/RemoteSettingsRepository.kt | 15 +- .../LockscreenOverlayAccessibilityService.kt | 2 +- .../service/ShellProxyService.kt | 20 +- .../ambientmusicmod/service/ShizukuService.kt | 91 ++-- .../recognition/RecognitionFragment.kt | 3 + .../utils/context/ShellContext.kt | 26 ++ .../utils/extensions/Extensions+Context.kt | 11 + .../extensions/Extensions+IActivityManager.kt | 84 ++++ .../Extensions+MusicRecognitionManager.kt | 16 + .../Extensions+ParcelFileDescriptor.kt | 27 ++ app/src/main/res/values/strings.xml | 3 +- ondemandoverlay/README.md | 5 + ondemandoverlay/module/customize.sh | 4 + ...usicRecognitionAttributionTagCallback.aidl | 8 + .../IMusicRecognitionManagerCallback.aidl | 13 + .../IMusicRecognitionService.aidl | 19 + .../IMusicRecognitionServiceCallback.aidl | 13 + .../java/android/app/IActivityManager.java | 29 +- .../musicrecognition/RecognitionRequest.java | 30 +- 24 files changed, 798 insertions(+), 47 deletions(-) create mode 100644 app/src/main/java/com/kieronquinn/app/ambientmusicmod/components/musicrecognition/RootMusicRecognitionManager.kt create mode 100644 app/src/main/java/com/kieronquinn/app/ambientmusicmod/utils/context/ShellContext.kt create mode 100644 app/src/main/java/com/kieronquinn/app/ambientmusicmod/utils/extensions/Extensions+IActivityManager.kt create mode 100644 app/src/main/java/com/kieronquinn/app/ambientmusicmod/utils/extensions/Extensions+MusicRecognitionManager.kt create mode 100644 app/src/main/java/com/kieronquinn/app/ambientmusicmod/utils/extensions/Extensions+ParcelFileDescriptor.kt create mode 100644 ondemandoverlay/README.md create mode 100644 systemstubs/src/main/aidl/android/media/musicrecognition/IMusicRecognitionAttributionTagCallback.aidl create mode 100644 systemstubs/src/main/aidl/android/media/musicrecognition/IMusicRecognitionManagerCallback.aidl create mode 100644 systemstubs/src/main/aidl/android/media/musicrecognition/IMusicRecognitionService.aidl create mode 100644 systemstubs/src/main/aidl/android/media/musicrecognition/IMusicRecognitionServiceCallback.aidl diff --git a/app/build.gradle b/app/build.gradle index 6bb941f..d1eb680 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,8 +13,8 @@ apply plugin: 'com.google.android.gms.oss-licenses-plugin' String DEFAULT_MANIFEST = "217:https://storage.googleapis.com/music-iq-db/updatable_ytm_db/20220626-030028/manifest.json" -def tagName = '2.0.3' -def version = 203 +def tagName = '2.0.4' +def version = 204 def getKeystoreProperties() { def properties = new Properties() diff --git a/app/src/main/aidl/com/kieronquinn/app/ambientmusicmod/IShellProxy.aidl b/app/src/main/aidl/com/kieronquinn/app/ambientmusicmod/IShellProxy.aidl index ec52a8b..a379888 100644 --- a/app/src/main/aidl/com/kieronquinn/app/ambientmusicmod/IShellProxy.aidl +++ b/app/src/main/aidl/com/kieronquinn/app/ambientmusicmod/IShellProxy.aidl @@ -18,7 +18,7 @@ interface IShellProxy { int AudioRecord_getBufferSizeInFrames() = 9; int AudioRecord_getSampleRate() = 10; - //MusicRecognitionManager proxy + //MusicRecognitionManager proxy (only used externally) void MusicRecognitionManager_beginStreamingSearch(in RecognitionRequest request, in IRecognitionCallback callback) = 11; //Sensor Privacy checks for AMM UI & to know when to not recognise @@ -41,6 +41,14 @@ interface IShellProxy { //Force stops Now Playing to force a reload of data oneway void forceStopNowPlaying() = 19; + //MusicRecognitionManager proxy with added thread injection (not exposed externally) + void MusicRecognitionManager_beginStreamingSearchWithThread( + in RecognitionRequest request, + in IRecognitionCallback callback, + in IBinder thread, + in IBinder token + ) = 20; + void destroy() = 16777114; } \ No newline at end of file diff --git a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/components/musicrecognition/RootMusicRecognitionManager.kt b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/components/musicrecognition/RootMusicRecognitionManager.kt new file mode 100644 index 0000000..e9ae90f --- /dev/null +++ b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/components/musicrecognition/RootMusicRecognitionManager.kt @@ -0,0 +1,394 @@ +package com.kieronquinn.app.ambientmusicmod.components.musicrecognition + +import android.Manifest +import android.annotation.SuppressLint +import android.app.AppOpsManager +import android.app.IActivityManager +import android.app.IApplicationThread +import android.app.IServiceConnection +import android.content.* +import android.content.pm.PackageManager +import android.content.pm.ServiceInfo +import android.media.AudioAttributes +import android.media.AudioFormat +import android.media.AudioRecord +import android.media.MediaMetadata +import android.media.musicrecognition.IMusicRecognitionManagerCallback +import android.media.musicrecognition.IMusicRecognitionService +import android.media.musicrecognition.IMusicRecognitionServiceCallback +import android.media.musicrecognition.MusicRecognitionManager.* +import android.media.musicrecognition.RecognitionRequest +import android.os.* +import android.util.Log +import androidx.annotation.NonNull +import androidx.annotation.Nullable +import androidx.annotation.RequiresApi +import com.kieronquinn.app.ambientmusicmod.PACKAGE_NAME_GSB +import com.kieronquinn.app.ambientmusicmod.utils.context.ShellContext +import com.kieronquinn.app.ambientmusicmod.utils.extensions.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import rikka.shizuku.SystemServiceHelper +import java.io.IOException +import java.io.OutputStream +import kotlin.coroutines.resume + +/** + * Root re-implementation of MusicRecognitionManager, directly binding the Google App's music + * recognition service and interacting with it. + * + * Requires audio recording via the `HOTWORD` mic to work. + */ +@RequiresApi(Build.VERSION_CODES.S) +class RootMusicRecognitionManager(private val context: Context, userId: Int) { + + companion object { + private const val DEBUG = false + private const val TAG = "RootMRM" + private const val SERVICE_CONNECT_TIMEOUT = 2500L + + // Number of bytes per sample of audio (which is a short). + private const val BYTES_PER_SAMPLE = 2 + private const val MAX_STREAMING_SECONDS = 24 + + private const val MUSIC_RECOGNITION_MANAGER_ATTRIBUTION_TAG = + "MusicRecognitionManagerService" + } + + private val appOpsManager = context.createAttributionContext( + MUSIC_RECOGNITION_MANAGER_ATTRIBUTION_TAG + ).getSystemService(AppOpsManager::class.java) + + private val attributionMessage = String.format( + "MusicRecognitionManager.invokedByUid.%s", userId + ) + + private val musicRecognitionIntent by lazy { + Intent("android.service.musicrecognition.MUSIC_RECOGNITION").apply { + `package` = PACKAGE_NAME_GSB + } + } + + private val activityManager by lazy { + val proxy = SystemServiceHelper.getSystemService("activity") + IActivityManager.Stub.asInterface(proxy) + } + + private var musicRecognitionBinder: IBinder? = null + private var musicRecognitionServiceConnection: IServiceConnection? = null + private val recognitionServiceLock = Mutex() + + suspend fun runStreamingSearch( + lifecycleScope: CoroutineScope, + request: RecognitionRequest, + callback: IMusicRecognitionManagerCallback, + thread: IBinder, + token: IBinder? + ) { + val serviceInfo = getServiceInfo() + if(serviceInfo == null){ + callback.onRecognitionFailed(RECOGNITION_FAILED_SERVICE_UNAVAILABLE) + return + } + val service = getMusicRecognitionService(IApplicationThread.Stub.asInterface(thread), token) + if(service == null){ + callback.onRecognitionFailed(RECOGNITION_FAILED_SERVICE_UNAVAILABLE) + return + } + val clientPipe = ParcelFileDescriptor_createPipe() + if(clientPipe == null){ + callback.onRecognitionFailed(RECOGNITION_FAILED_AUDIO_UNAVAILABLE) + return + } + val serviceCallback = MusicRecognitionServiceCallback(callback) + val audioSink = clientPipe.second + val clientRead = clientPipe.first + val attributionTag = service.getAttributionTag() + lifecycleScope.streamAudio(serviceInfo, attributionTag, request, callback, audioSink) + service.onAudioStreamStarted(clientRead, request.audioFormat, serviceCallback) + } + + /** + * Streams audio based on given request to the given audioSink. Notifies callback of errors. + * + * @param recognitionRequest the recognition request specifying audio parameters. + * @param clientCallback the callback to notify on errors. + * @param audioSink the sink to which to stream audio to. + */ + private fun CoroutineScope.streamAudio( + serviceInfo: ServiceInfo, + @Nullable attributionTag: String, + @NonNull recognitionRequest: RecognitionRequest, + clientCallback: IMusicRecognitionManagerCallback, + audioSink: ParcelFileDescriptor + ) = launch { + val maxAudioLengthSeconds: Int = + recognitionRequest.maxAudioLengthSeconds.coerceAtMost(MAX_STREAMING_SECONDS) + if (maxAudioLengthSeconds <= 0) { + // TODO(b/192992319): A request to stream 0s of audio can be used to initialize the + // music recognition service implementation, hence not reporting an error here. + // The TODO for Android T is to move this functionality into an init() API call. + Log_i("No audio requested. Closing stream.") + try { + audioSink.close() + clientCallback.onAudioStreamClosed() + } catch (e: IOException) { + Log_e("Problem closing stream.", e) + } catch (ignored: RemoteException) { + // Ignored. + } + return@launch + } + try { + startRecordAudioOp(serviceInfo, attributionTag) + } catch (e: SecurityException) { + // A security exception can occur if the MusicRecognitionService (receiving the audio) + // does not (or does no longer) hold the necessary permissions to record audio. + Log_e("RECORD_AUDIO op not permitted on behalf of service") + try { + clientCallback.onRecognitionFailed(RECOGNITION_FAILED_AUDIO_UNAVAILABLE) + } catch (ignored: RemoteException) { + // Ignored. + } + return@launch + } + val audioRecord: AudioRecord = createAudioRecord(recognitionRequest, maxAudioLengthSeconds) + try { + ParcelFileDescriptor.AutoCloseOutputStream(audioSink).use { fos -> + streamAudio(recognitionRequest, maxAudioLengthSeconds, audioRecord, fos) + } + } catch (e: IOException) { + Log_e("Audio streaming stopped.", e) + } finally { + finishRecordAudioOp(serviceInfo, attributionTag) + audioRecord.release() + try { + clientCallback.onAudioStreamClosed() + } catch (ignored: RemoteException) { + // Ignored. + } + } + } + + /** Performs the actual streaming from audioRecord into outputStream. */ + @Throws(IOException::class) + private fun streamAudio( + @NonNull recognitionRequest: RecognitionRequest, + maxAudioLengthSeconds: Int, audioRecord: AudioRecord, outputStream: OutputStream + ) { + val halfSecondBufferSize = audioRecord.bufferSizeInFrames / maxAudioLengthSeconds + val byteBuffer = ByteArray(halfSecondBufferSize) + var bytesRead = 0 + var totalBytesRead = 0 + var ignoreBytes: Int = recognitionRequest.ignoreBeginningFrames * BYTES_PER_SAMPLE + audioRecord.startRecording() + while (bytesRead >= 0 && (totalBytesRead < audioRecord.bufferSizeInFrames * BYTES_PER_SAMPLE) && musicRecognitionBinder != null) { + bytesRead = audioRecord.read(byteBuffer, 0, byteBuffer.size) + if (bytesRead > 0) { + totalBytesRead += bytesRead + // If we are ignoring the first x bytes, update that counter. + if (ignoreBytes > 0) { + ignoreBytes -= bytesRead + // If we've dipped negative, we've skipped through all ignored bytes + // and then some. Write out the bytes we shouldn't have skipped. + if (ignoreBytes < 0) { + outputStream.write(byteBuffer, bytesRead + ignoreBytes, -ignoreBytes) + } + } else { + outputStream.write(byteBuffer) + } + } + } + Log_i(String.format("Streamed %s bytes from audio record", totalBytesRead)) + } + + /** + * Tracks that the RECORD_AUDIO operation started (attributes it to the service receiving the + * audio). + */ + private fun startRecordAudioOp(serviceInfo: ServiceInfo, attributionTag: String?) { + val status: Int = appOpsManager.startProxyOp( + AppOpsManager.permissionToOp(Manifest.permission.RECORD_AUDIO)!!, + serviceInfo.applicationInfo.uid, + serviceInfo.packageName, + attributionTag, + attributionMessage + ) + // The above should already throw a SecurityException. This is just a fallback. + if (status != AppOpsManager.MODE_ALLOWED) { + throw SecurityException(String.format( + "Failed to obtain RECORD_AUDIO permission (status: %d) for " + + "receiving service: %s", status, serviceInfo.name)) + } + Log_i(String.format( + "Starting audio streaming. Attributing to %s (%d) with tag '%s'", + serviceInfo.packageName, serviceInfo.applicationInfo.uid, attributionTag + ) + ) + } + + /** Tracks that the RECORD_AUDIO operation finished. */ + private fun finishRecordAudioOp(serviceInfo: ServiceInfo, attributionTag: String?) { + appOpsManager.finishProxyOp( + AppOpsManager.permissionToOp(Manifest.permission.RECORD_AUDIO)!!, + serviceInfo.applicationInfo.uid, + serviceInfo.packageName, + attributionTag + ) + } + + private suspend fun getMusicRecognitionService( + thread: IApplicationThread, + token: IBinder? + ) = recognitionServiceLock.withLock { + suspendCancellableCoroutineWithTimeout(SERVICE_CONNECT_TIMEOUT) { resume -> + var hasResumed = false + musicRecognitionBinder?.let { + if(hasResumed || !it.pingBinder()) return@let + resume.resume(IMusicRecognitionService.Stub.asInterface(it)) + hasResumed = true + return@suspendCancellableCoroutineWithTimeout + } + var dispatcher: IServiceConnection? = null + val serviceConnection = object: ServiceConnection { + override fun onServiceConnected(component: ComponentName, binder: IBinder) { + musicRecognitionBinder = binder + musicRecognitionServiceConnection = dispatcher + if(!hasResumed) { + resume.resume(IMusicRecognitionService.Stub.asInterface(binder)) + } + hasResumed = true + } + + override fun onServiceDisconnected(component: ComponentName) { + musicRecognitionServiceConnection = null + musicRecognitionBinder = null + } + } + dispatcher = context.getServiceDispatcher(serviceConnection, 0) + activityManager.bindServiceInstanceCompat( + context, + dispatcher, + thread, + token, + musicRecognitionIntent, + Context.BIND_AUTO_CREATE + ) + } + } + + /** Establishes an audio stream from the DSP audio source. */ + @SuppressLint("MissingPermission") + private fun createAudioRecord( + recognitionRequest: RecognitionRequest, + maxAudioLengthSeconds: Int + ): AudioRecord { + val sampleRate: Int = recognitionRequest.audioFormat.sampleRate + val bufferSize: Int = getBufferSizeInBytes(sampleRate, maxAudioLengthSeconds) + val shellContext = ShellContext(context, true) + //We need to replace the attributes as we can't access the regular mic + val attributes = AudioAttributes.Builder().apply { + AudioAttributes.Builder::class.java.getMethod("setInternalCapturePreset", Integer.TYPE) + .invoke(this, 0x7CF) + }.build() + return AudioRecord::class.java.getDeclaredConstructor( + AudioAttributes::class.java, //attributes + AudioFormat::class.java, // format + Integer.TYPE, // bufferSizeInBytes + Integer.TYPE, // sessionId + Context::class.java, // context + Integer.TYPE // maxSharedAudioHistoryMs + ).apply { + isAccessible = true + }.newInstance( + attributes, + recognitionRequest.audioFormat, + bufferSize, + recognitionRequest.captureSession, + shellContext, + 0 + ) + } + + /** + * Returns the number of bytes required to store `bufferLengthSeconds` of audio sampled at + * `sampleRate` Hz, using the format returned by DSP audio capture. + */ + private fun getBufferSizeInBytes(sampleRate: Int, bufferLengthSeconds: Int): Int { + return BYTES_PER_SAMPLE * sampleRate * bufferLengthSeconds + } + + private fun destroyService() { + musicRecognitionServiceConnection?.let { activityManager.unbindService(it) } + musicRecognitionBinder = null + } + + /** Removes remote objects from the bundle. */ + private fun sanitizeBundle(@Nullable bundle: Bundle?) { + if (bundle == null) { + return + } + for (key in bundle.keySet()) { + val o = bundle[key] + if (o is Bundle) { + sanitizeBundle(o as Bundle?) + } else if (o is IBinder || o is ParcelFileDescriptor) { + bundle.remove(key) + } + } + } + + private fun getServiceInfo(): ServiceInfo? { + return try { + context.packageManager.resolveService(musicRecognitionIntent, 0)?.serviceInfo + }catch (e: PackageManager.NameNotFoundException){ + null + } + } + + /** + * Callback invoked by [android.service.musicrecognition.MusicRecognitionService] to pass + * back the music search result. + */ + inner class MusicRecognitionServiceCallback(private val clientCallback: IMusicRecognitionManagerCallback) : + IMusicRecognitionServiceCallback.Stub() { + + override fun onRecognitionSucceeded(result: MediaMetadata, extras: Bundle) { + try { + sanitizeBundle(extras) + clientCallback.onRecognitionSucceeded(result, extras) + } catch (ignored: RemoteException) { + // Ignored. + } + destroyService() + } + + override fun onRecognitionFailed(@RecognitionFailureCode failureCode: Int) { + try { + clientCallback.onRecognitionFailed(failureCode) + } catch (ignored: RemoteException) { + // Ignored. + } + destroyService() + } + + } + + private fun Log_i(text: String) { + if(!DEBUG) return + Log.i(TAG, text) + } + + private fun Log_e(text: String) { + if(!DEBUG) return + Log.e(TAG, text) + } + + private fun Log_e(text: String, exception: Exception) { + if(!DEBUG) return + Log.e(TAG, text, exception) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/model/backup/SettingsBackup.kt b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/model/backup/SettingsBackup.kt index 4f56c08..4c43f86 100644 --- a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/model/backup/SettingsBackup.kt +++ b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/model/backup/SettingsBackup.kt @@ -63,6 +63,11 @@ data class SettingsBackup( @SerializedName("recording_gain") val recordingGain: Float?, @SerializedName("show_album_art") - val showAlbumArt: Boolean? - //Sensor Privacy suppression intentionally not backed up + val showAlbumArt: Boolean?, + @SerializedName("overlay_text_colour") + val overlayTextColour: OverlayTextColour?, + @SerializedName("overlay_custom_text_colour") + val overlayCustomTextColour: Int?, + @SerializedName("alternative_encoding") + val alternativeEncoding: Boolean? ) \ No newline at end of file diff --git a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/repositories/BackupRestoreRepository.kt b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/repositories/BackupRestoreRepository.kt index 737eb2a..f37005f 100644 --- a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/repositories/BackupRestoreRepository.kt +++ b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/repositories/BackupRestoreRepository.kt @@ -329,7 +329,10 @@ class BackupRestoreRepositoryImpl( deviceConfigRepository.superpacksRequireCharging.getOrNull(), deviceConfigRepository.superpacksRequireWiFi.getOrNull(), deviceConfigRepository.recordingGain.getOrNull(), - deviceConfigRepository.showAlbumArt.getOrNull() + deviceConfigRepository.showAlbumArt.getOrNull(), + settingsRepository.lockscreenOverlayColour.getOrNull(), + settingsRepository.lockscreenOverlayCustomColour.getOrNull(), + deviceConfigRepository.alternativeEncoding.getOrNull() ) } @@ -366,6 +369,10 @@ class BackupRestoreRepositoryImpl( superpacksRequireWiFi.restoreTo(deviceConfigRepository.superpacksRequireWiFi) recordingGain.restoreTo(deviceConfigRepository.recordingGain) showAlbumArt.restoreTo(deviceConfigRepository.showAlbumArt) + + overlayTextColour.restoreTo(settingsRepository.lockscreenOverlayColour) + overlayCustomTextColour.restoreTo(settingsRepository.lockscreenOverlayCustomColour) + alternativeEncoding.restoreTo(deviceConfigRepository.alternativeEncoding) } private suspend fun T?.restoreTo(setting: BaseSettingsRepository.AmbientMusicModSetting) { diff --git a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/repositories/RemoteSettingsRepository.kt b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/repositories/RemoteSettingsRepository.kt index 24287d0..f818271 100644 --- a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/repositories/RemoteSettingsRepository.kt +++ b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/repositories/RemoteSettingsRepository.kt @@ -96,9 +96,6 @@ class RemoteSettingsRepositoryImpl( ): RemoteSettingsRepository { companion object { - private const val COMPONENT_GSA_ON_DEMAND = - "com.google.android.googlequicksearchbox/com.google.android.apps.search.soundsearch.service.SoundSearchService" - private const val SETTINGS_AUTHORITY = "com.google.android.as.pam.ambientmusic.settings" private val SETTINGS_URI = Uri.Builder().apply { scheme("content") @@ -217,13 +214,14 @@ class RemoteSettingsRepositoryImpl( }?.firstOrNull() }.flowOn(Dispatchers.IO) - private fun isGoogleAppSupported(): GoogleAppState { + private suspend fun isGoogleAppSupported(): GoogleAppState { //Requires Android 12+ if(Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return GoogleAppState.UNSUPPORTED //Requires ARMv8 if(isArmv7) return GoogleAppState.UNSUPPORTED //Requires the config value to point to it - if(!isOnDemandConfigValueSet) return GoogleAppState.NEEDS_OVERLAY + val isRoot = shizukuServiceRepository.runWithService { it.isRoot }.unwrap() ?: false + if(!isOnDemandConfigValueSet && !isRoot) return GoogleAppState.NEEDS_OVERLAY val splits = packageManager.getSplits(PACKAGE_NAME_GSB) //The QSB app needs Sound Search to be installed, this has to be done manually if(!splits.contains("sound_search_fingerprinter_split")) return GoogleAppState.NEEDS_SPLIT @@ -336,11 +334,6 @@ class RemoteSettingsRepositoryImpl( supported == GoogleAppState.SUPPORTED && settings?.onDemandEnabled == true } - private fun Context.isOnDemandConfigValueSet(): Boolean { - return getString( - "android", - "config_defaultMusicRecognitionService" - ) == COMPONENT_GSA_ON_DEMAND - } + } \ No newline at end of file diff --git a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/service/LockscreenOverlayAccessibilityService.kt b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/service/LockscreenOverlayAccessibilityService.kt index cd4e939..44c1388 100644 --- a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/service/LockscreenOverlayAccessibilityService.kt +++ b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/service/LockscreenOverlayAccessibilityService.kt @@ -218,7 +218,7 @@ class LockscreenOverlayAccessibilityService : LifecycleAccessibilityService() { * may be in front of it. */ private fun isLockscreenVisible(): Boolean { - return powerManager.isInteractive && keyguardManager.isDeviceLocked + return powerManager.isInteractive && keyguardManager.isKeyguardLocked } private fun Array.or(): Int { diff --git a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/service/ShellProxyService.kt b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/service/ShellProxyService.kt index b7050bc..45da527 100644 --- a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/service/ShellProxyService.kt +++ b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/service/ShellProxyService.kt @@ -11,6 +11,8 @@ import com.kieronquinn.app.ambientmusicmod.IRecognitionCallback import com.kieronquinn.app.ambientmusicmod.IShellProxy import com.kieronquinn.app.ambientmusicmod.repositories.ShizukuServiceRepository import com.kieronquinn.app.ambientmusicmod.repositories.ShizukuServiceRepository.ShizukuServiceResponse +import com.kieronquinn.app.ambientmusicmod.utils.extensions.getActivityToken +import com.kieronquinn.app.ambientmusicmod.utils.extensions.getApplicationThread import org.koin.android.ext.android.inject /** @@ -99,8 +101,15 @@ class ShellProxyService: Service() { request: RecognitionRequest?, callback: IRecognitionCallback? ) { + val thread = getApplicationThread().asBinder() + val token = getActivityToken() runWithService { - it.MusicRecognitionManager_beginStreamingSearch(request, callback) + it.MusicRecognitionManager_beginStreamingSearchWithThread( + request, + callback, + thread, + token + ) } } @@ -156,6 +165,15 @@ class ShellProxyService: Service() { throw SecurityException("Not exposed to external access") } + override fun MusicRecognitionManager_beginStreamingSearchWithThread( + request: RecognitionRequest?, + callback: IRecognitionCallback?, + thread: IBinder?, + token: IBinder? + ) { + throw SecurityException("Not exposed to external access") + } + override fun destroy() { //No-op at this level } diff --git a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/service/ShizukuService.kt b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/service/ShizukuService.kt index 65f1e5e..214ac65 100644 --- a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/service/ShizukuService.kt +++ b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/service/ShizukuService.kt @@ -1,7 +1,6 @@ package com.kieronquinn.app.ambientmusicmod.service import android.annotation.SuppressLint -import android.content.AttributionSource import android.content.Context import android.content.ContextWrapper import android.hardware.SensorPrivacyManager.Sensors @@ -11,8 +10,8 @@ import android.media.AudioAttributes import android.media.AudioFormat import android.media.AudioRecord import android.media.MediaMetadata +import android.media.musicrecognition.IMusicRecognitionManagerCallback import android.media.musicrecognition.MusicRecognitionManager -import android.media.musicrecognition.MusicRecognitionManager.RECOGNITION_FAILED_SERVICE_UNAVAILABLE import android.media.musicrecognition.RecognitionRequest import android.os.* import android.view.IWindowManager @@ -20,10 +19,13 @@ import androidx.core.os.BuildCompat import com.android.internal.policy.IKeyguardDismissCallback import com.android.internal.widget.ILockSettings import com.kieronquinn.app.ambientmusicmod.* +import com.kieronquinn.app.ambientmusicmod.components.musicrecognition.RootMusicRecognitionManager +import com.kieronquinn.app.ambientmusicmod.utils.context.ShellContext import com.kieronquinn.app.ambientmusicmod.utils.extensions.* import dev.rikka.tools.refine.Refine import kotlinx.coroutines.MainScope import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch import rikka.shizuku.SystemServiceHelper import java.util.* import java.util.concurrent.Executors @@ -33,9 +35,10 @@ import kotlin.system.exitProcess class ShizukuService: IShellProxy.Stub() { companion object { - private const val SHELL_UID = 2000 - private const val ROOT_UID = 0 - private const val SHELL_PACKAGE = "com.android.shell" + const val ROOT_UID = 0 + const val SHELL_UID = 2000 + const val ROOT_PACKAGE = "root" + const val SHELL_PACKAGE = "com.android.shell" } private val context by lazy { @@ -58,6 +61,12 @@ class ShizukuService: IShellProxy.Stub() { private val musicRecognitionManagerExecutor = Executors.newSingleThreadExecutor() private val sensorPrivacyListeners = HashMap() + private val rootMusicRecognitionManager by lazy { + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){ + RootMusicRecognitionManager(context, getUserId()) + }else null + } + private val musicRecognitionManager by lazy { context.getSystemService("music_recognition") as MusicRecognitionManager } @@ -175,7 +184,7 @@ class ShizukuService: IShellProxy.Stub() { sessionId: Int, bufferSizeInBytes: Int ): AudioRecord { - val shellContext = ShellContext(context) + val shellContext = ShellContext(context, isRoot) return AudioRecord::class.java.getDeclaredConstructor( AudioAttributes::class.java, //attributes AudioFormat::class.java, // format @@ -213,29 +222,35 @@ class ShizukuService: IShellProxy.Stub() { private fun replaceBaseContextIfRequired() { val base = getActivityThreadApplication().getBase() if(base !is ContextWrapper){ - getActivityThreadApplication().setBase(ShellContext(base)) + getActivityThreadApplication().setBase(ShellContext(base, isRoot)) } } - private inner class ShellContext(context: Context): ContextWrapper(context) { - - override fun getBaseContext(): Context { - return super.getBaseContext() - } - - override fun getOpPackageName(): String { - return "uid:0" - } + override fun MusicRecognitionManager_beginStreamingSearch( + request: RecognitionRequest, + callback: IRecognitionCallback + ) { + //No longer used, will be redirected to beginStreamingSearchWithThread from the proxy service + } - @SuppressLint("NewApi") - override fun getAttributionSource(): AttributionSource { - val uid = if(isRoot) ROOT_UID else SHELL_UID - return AttributionSource.Builder(uid) - .setPackageName(SHELL_PACKAGE).build() + override fun MusicRecognitionManager_beginStreamingSearchWithThread( + request: RecognitionRequest, + callback: IRecognitionCallback, + thread: IBinder, + token: IBinder? + ) { + if(context.isOnDemandConfigValueSet()) { + beginStreamingSearchViaSystem(request, callback) + }else{ + if(!isRoot){ + callback.onRecognitionFailed(request, MusicRecognitionManager_RECOGNITION_FAILED_NEEDS_ROOT) + }else{ + beginStreamingSearchViaRoot(request, callback, thread, token) + } } } - override fun MusicRecognitionManager_beginStreamingSearch( + private fun beginStreamingSearchViaSystem( request: RecognitionRequest, callback: IRecognitionCallback ) = runWithClearedIdentity { @@ -266,9 +281,37 @@ class ShizukuService: IShellProxy.Stub() { systemCallback ) }catch (e: Exception){ - callback.onRecognitionFailed(request, RECOGNITION_FAILED_SERVICE_UNAVAILABLE) + callback.onRecognitionFailed(request, + MusicRecognitionManager.RECOGNITION_FAILED_SERVICE_UNAVAILABLE + ) + } + } + + @SuppressLint("NewApi") + private fun beginStreamingSearchViaRoot( + request: RecognitionRequest, + callback: IRecognitionCallback, + thread: IBinder, + token: IBinder? + ) = runWithClearedIdentity { + scope.launch { + val managerCallback = object: IMusicRecognitionManagerCallback.Stub() { + override fun onRecognitionSucceeded(result: MediaMetadata?, extras: Bundle?) { + callback.onRecognitionSucceeded(request, result, extras) + } + + override fun onRecognitionFailed(failureCode: Int) { + callback.onRecognitionFailed(request, failureCode) + } + + override fun onAudioStreamClosed() { + callback.onAudioStreamClosed() + } + } + rootMusicRecognitionManager?.runStreamingSearch( + scope, request, managerCallback, thread, token + ) } - Unit } override fun ping(): Boolean { diff --git a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/ui/screens/recognition/RecognitionFragment.kt b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/ui/screens/recognition/RecognitionFragment.kt index eebbba0..a5ec45c 100644 --- a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/ui/screens/recognition/RecognitionFragment.kt +++ b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/ui/screens/recognition/RecognitionFragment.kt @@ -470,6 +470,9 @@ class RecognitionFragment: BoundDialogFragment(Fragm MusicRecognitionManager.RECOGNITION_FAILED_TIMEOUT -> { R.string.recognition_failed_reason_content_code_timeout } + MusicRecognitionManager_RECOGNITION_FAILED_NEEDS_ROOT -> { + R.string.recognition_failed_reason_content_root + } else -> { R.string.recognition_failed_reason_content_code_unknown } diff --git a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/utils/context/ShellContext.kt b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/utils/context/ShellContext.kt new file mode 100644 index 0000000..4e4ced9 --- /dev/null +++ b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/utils/context/ShellContext.kt @@ -0,0 +1,26 @@ +package com.kieronquinn.app.ambientmusicmod.utils.context + +import android.annotation.SuppressLint +import android.content.AttributionSource +import android.content.Context +import android.content.ContextWrapper +import android.util.Log +import com.kieronquinn.app.ambientmusicmod.service.ShizukuService.Companion.ROOT_PACKAGE +import com.kieronquinn.app.ambientmusicmod.service.ShizukuService.Companion.ROOT_UID +import com.kieronquinn.app.ambientmusicmod.service.ShizukuService.Companion.SHELL_UID + +class ShellContext(context: Context, private val isRoot: Boolean) : ContextWrapper(context) { + + override fun getOpPackageName(): String { + return "uid:${if(isRoot) ROOT_UID else SHELL_UID}" + } + + @SuppressLint("NewApi") + override fun getAttributionSource(): AttributionSource { + val uid = if (isRoot) ROOT_UID else SHELL_UID + return AttributionSource.Builder(uid) + .setPackageName(if(isRoot) "android" else ROOT_PACKAGE).build().also { + Log.d("RootMRM", "Getting attribution source, returning UID ${it.uid}") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/utils/extensions/Extensions+Context.kt b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/utils/extensions/Extensions+Context.kt index 85422a4..ae76a1b 100644 --- a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/utils/extensions/Extensions+Context.kt +++ b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/utils/extensions/Extensions+Context.kt @@ -22,6 +22,7 @@ import androidx.annotation.AttrRes import androidx.annotation.ColorInt import androidx.core.content.ContextCompat import com.kieronquinn.app.ambientmusicmod.repositories.RemoteSettingsRepository +import com.kieronquinn.app.ambientmusicmod.repositories.RemoteSettingsRepositoryImpl import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow @@ -285,4 +286,14 @@ fun Context.getColorResCompat(@AttrRes id: Int): Int { this.theme.resolveAttribute(id, resolvedAttr, true) val colorRes = resolvedAttr.run { if (resourceId != 0) resourceId else data } return ContextCompat.getColor(this, colorRes) +} + +private const val COMPONENT_GSA_ON_DEMAND = + "com.google.android.googlequicksearchbox/com.google.android.apps.search.soundsearch.service.SoundSearchService" + +fun Context.isOnDemandConfigValueSet(): Boolean { + return getString( + "android", + "config_defaultMusicRecognitionService" + ) == COMPONENT_GSA_ON_DEMAND } \ No newline at end of file diff --git a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/utils/extensions/Extensions+IActivityManager.kt b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/utils/extensions/Extensions+IActivityManager.kt new file mode 100644 index 0000000..6838620 --- /dev/null +++ b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/utils/extensions/Extensions+IActivityManager.kt @@ -0,0 +1,84 @@ +package com.kieronquinn.app.ambientmusicmod.utils.extensions + +import android.annotation.SuppressLint +import android.app.IActivityManager +import android.app.IApplicationThread +import android.app.IServiceConnection +import android.content.Context +import android.content.Intent +import android.os.IBinder +import android.os.UserHandle +import android.util.Log +import androidx.core.os.BuildCompat + +fun IActivityManager.bindServiceInstanceCompat( + context: Context, + serviceConnection: IServiceConnection, + thread: IApplicationThread, + token: IBinder?, + intent: Intent, + flags: Int +): Int { + try { + val packageName = Context::class.java.getMethod("getOpPackageName") + .invoke(context) as String + val userHandle = Context::class.java.getMethod("getUser").invoke(context) as UserHandle + val identifier = + UserHandle::class.java.getMethod("getIdentifier").invoke(userHandle) as Int + Intent::class.java.getMethod("prepareToLeaveProcess", Context::class.java) + .invoke(intent, context) + return bindServiceInstanceCompat( + thread, + token, + intent, + null, + serviceConnection, + flags, + null, + packageName, + identifier + ) + } catch (e: Exception) { + Log.e("ServiceBind", "Error binding service", e) + return 0 + } +} + +@SuppressLint("UnsafeOptInUsageError") +private fun IActivityManager.bindServiceInstanceCompat( + caller: IApplicationThread?, + token: IBinder?, + service: Intent?, + resolvedType: String?, + connection: IServiceConnection?, + flags: Int, + instanceName: String?, + callingPackage: String?, + userId: Int +): Int { + return if (BuildCompat.isAtLeastT()) { + bindServiceInstance( + caller, + token, + service, + resolvedType, + connection, + flags, + instanceName, + callingPackage, + userId + ) + } else { + bindIsolatedService( + caller, + token, + service, + resolvedType, + connection, + flags, + instanceName, + callingPackage, + userId + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/utils/extensions/Extensions+MusicRecognitionManager.kt b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/utils/extensions/Extensions+MusicRecognitionManager.kt new file mode 100644 index 0000000..e7e8b24 --- /dev/null +++ b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/utils/extensions/Extensions+MusicRecognitionManager.kt @@ -0,0 +1,16 @@ +package com.kieronquinn.app.ambientmusicmod.utils.extensions + +import android.media.musicrecognition.IMusicRecognitionAttributionTagCallback +import android.media.musicrecognition.IMusicRecognitionService +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlin.coroutines.resume + +const val MusicRecognitionManager_RECOGNITION_FAILED_NEEDS_ROOT: Int = -2 + +suspend fun IMusicRecognitionService.getAttributionTag() = suspendCancellableCoroutine { + getAttributionTag(object: IMusicRecognitionAttributionTagCallback.Stub() { + override fun onAttributionTag(attributionTag: String) { + it.resume(attributionTag) + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/utils/extensions/Extensions+ParcelFileDescriptor.kt b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/utils/extensions/Extensions+ParcelFileDescriptor.kt new file mode 100644 index 0000000..6bd77b1 --- /dev/null +++ b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/utils/extensions/Extensions+ParcelFileDescriptor.kt @@ -0,0 +1,27 @@ +package com.kieronquinn.app.ambientmusicmod.utils.extensions + +import android.os.ParcelFileDescriptor +import android.util.Log +import java.io.IOException + +fun ParcelFileDescriptor_createPipe(): Pair? { + val fileDescriptors: Array = try { + ParcelFileDescriptor.createPipe() + } catch (e: IOException) { + Log.e("PFD", "Failed to create audio stream pipe", e) + return null + } + if (fileDescriptors.size != 2) { + Log.e("PFD", "Failed to create audio stream pipe, " + + "unexpected number of file descriptors") + return null + } + if (!fileDescriptors[0].fileDescriptor.valid() + || !fileDescriptors[1].fileDescriptor.valid() + ) { + Log.e("PFD", "Failed to create audio stream pipe, didn't " + + "receive a pair of valid file descriptors.") + return null + } + return Pair(fileDescriptors[0], fileDescriptors[1]) +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 82023d8..830cbd5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -124,6 +124,7 @@ Music is playing Failed to record audio, please try again A recognition is already running and could not be re-used, please try again + Root or a Magisk Module is required to use On Demand on this device, please read the FAQ An unknown error occurred, please try again No track matched, please try again No network connection, try again or use on device recognition @@ -171,7 +172,7 @@ The version of the Google App installed on this device is not compatible with On Demand, please follow the steps on the setup page to replace it with a version that is. Setup Firmware Incompatible - The firmware on your device is missing a crucial configuration to point to the On Demand service. This can be fixed with a Magisk Module if your device is rooted, please follow the steps on the setup page to install. + The firmware on your device is missing a crucial configuration to point to the On Demand service. This can be fixed by installing a Magisk Module, starting Shizuku as root or using Sui (which requires root), please follow the steps on the setup page to install. Setup Disable Save On Demand Recognitions diff --git a/ondemandoverlay/README.md b/ondemandoverlay/README.md new file mode 100644 index 0000000..a20d27e --- /dev/null +++ b/ondemandoverlay/README.md @@ -0,0 +1,5 @@ +## On Demand Overlay + +This directory contains a Magisk module for enabling the On Demand Music recognition service. + +`customize.sh` contains code based on and modified from [QuickSwitch](https://github.com/skittles9823/QuickSwitch), to dynamically copy the APK to the right folder. \ No newline at end of file diff --git a/ondemandoverlay/module/customize.sh b/ondemandoverlay/module/customize.sh index b9fdc4d..5922baf 100644 --- a/ondemandoverlay/module/customize.sh +++ b/ondemandoverlay/module/customize.sh @@ -1,5 +1,8 @@ # OVERLAY CODE MODIFIED FROM QuickSwitch: https://github.com/skittles9823/QuickSwitch/blob/master/quickswitch +# Global vars +API=$(getprop ro.build.version.sdk) + # Magisk Module ID ID="OnDemandOverlay" @@ -74,6 +77,7 @@ copyOverlay() { ui_print "- Installing overlay to $STEPDIR" ui_print "" ui_print "" + mkdir -p $STEPDIR cp -rf ${MODDIR}/install/OnDemandOverlay.apk ${STEPDIR} rm -r ${MODDIR}/install } diff --git a/systemstubs/src/main/aidl/android/media/musicrecognition/IMusicRecognitionAttributionTagCallback.aidl b/systemstubs/src/main/aidl/android/media/musicrecognition/IMusicRecognitionAttributionTagCallback.aidl new file mode 100644 index 0000000..906a97c --- /dev/null +++ b/systemstubs/src/main/aidl/android/media/musicrecognition/IMusicRecognitionAttributionTagCallback.aidl @@ -0,0 +1,8 @@ +package android.media.musicrecognition; + +/** + * Interface from {@link MusicRecognitionService} to system to pass attribution tag. + */ +oneway interface IMusicRecognitionAttributionTagCallback { + void onAttributionTag(in String attributionTag); +} \ No newline at end of file diff --git a/systemstubs/src/main/aidl/android/media/musicrecognition/IMusicRecognitionManagerCallback.aidl b/systemstubs/src/main/aidl/android/media/musicrecognition/IMusicRecognitionManagerCallback.aidl new file mode 100644 index 0000000..927473d --- /dev/null +++ b/systemstubs/src/main/aidl/android/media/musicrecognition/IMusicRecognitionManagerCallback.aidl @@ -0,0 +1,13 @@ +package android.media.musicrecognition; + +import android.os.Bundle; +import android.media.MediaMetadata; + +/** + * Callback used by system server to notify invoker of {@link MusicRecognitionManager} of the result + */ +oneway interface IMusicRecognitionManagerCallback { + void onRecognitionSucceeded(in MediaMetadata result, in Bundle extras); + void onRecognitionFailed(int failureCode); + void onAudioStreamClosed(); +} \ No newline at end of file diff --git a/systemstubs/src/main/aidl/android/media/musicrecognition/IMusicRecognitionService.aidl b/systemstubs/src/main/aidl/android/media/musicrecognition/IMusicRecognitionService.aidl new file mode 100644 index 0000000..5c33af6 --- /dev/null +++ b/systemstubs/src/main/aidl/android/media/musicrecognition/IMusicRecognitionService.aidl @@ -0,0 +1,19 @@ +package android.media.musicrecognition; + +import android.media.AudioFormat; +import android.os.ParcelFileDescriptor; +import android.os.IBinder; +import android.media.musicrecognition.IMusicRecognitionServiceCallback; +import android.media.musicrecognition.IMusicRecognitionAttributionTagCallback; + +/** + * Interface from the system to a {@link MusicRecognitionService}. + */ +oneway interface IMusicRecognitionService { + void onAudioStreamStarted( + in ParcelFileDescriptor fd, + in AudioFormat audioFormat, + in IMusicRecognitionServiceCallback callback); + + void getAttributionTag(in IMusicRecognitionAttributionTagCallback callback); +} \ No newline at end of file diff --git a/systemstubs/src/main/aidl/android/media/musicrecognition/IMusicRecognitionServiceCallback.aidl b/systemstubs/src/main/aidl/android/media/musicrecognition/IMusicRecognitionServiceCallback.aidl new file mode 100644 index 0000000..e6e80e4 --- /dev/null +++ b/systemstubs/src/main/aidl/android/media/musicrecognition/IMusicRecognitionServiceCallback.aidl @@ -0,0 +1,13 @@ +package android.media.musicrecognition; + +import android.os.Bundle; +import android.media.MediaMetadata; + +/** + * Interface from a {@MusicRecognitionService} to the system. + */ +oneway interface IMusicRecognitionServiceCallback { + void onRecognitionSucceeded(in MediaMetadata result, in Bundle extras); + + void onRecognitionFailed(int failureCode); +} \ No newline at end of file diff --git a/systemstubs/src/main/java/android/app/IActivityManager.java b/systemstubs/src/main/java/android/app/IActivityManager.java index 42ff1c2..b8a1783 100644 --- a/systemstubs/src/main/java/android/app/IActivityManager.java +++ b/systemstubs/src/main/java/android/app/IActivityManager.java @@ -1,15 +1,42 @@ package android.app; +import android.content.Intent; +import android.os.IBinder; import android.os.IInterface; public interface IActivityManager extends IInterface { abstract class Stub extends android.os.Binder implements android.app.IServiceConnection { - public static IActivityManager asInterface(android.os.IBinder obj) + public static IActivityManager asInterface(android.os.IBinder binder) { throw new RuntimeException("Stub!"); } } + int bindIsolatedService( + IApplicationThread caller, + IBinder token, + Intent service, + String resolvedType, + IServiceConnection connection, + int flags, + String instanceName, + String callingPackage, + int userId); + + //Android 13 + int bindServiceInstance( + IApplicationThread caller, + IBinder token, + Intent service, + String resolvedType, + IServiceConnection connection, + int flags, + String instanceName, + String callingPackage, + int userId); + + boolean unbindService(IServiceConnection serviceConnection); + } diff --git a/systemstubs/src/main/java/android/media/musicrecognition/RecognitionRequest.java b/systemstubs/src/main/java/android/media/musicrecognition/RecognitionRequest.java index 37f7f18..3401b2a 100644 --- a/systemstubs/src/main/java/android/media/musicrecognition/RecognitionRequest.java +++ b/systemstubs/src/main/java/android/media/musicrecognition/RecognitionRequest.java @@ -1,11 +1,13 @@ package android.media.musicrecognition; +import android.media.AudioAttributes; +import android.media.AudioFormat; import android.os.Parcel; import android.os.Parcelable; -public class RecognitionRequest implements Parcelable { +import androidx.annotation.NonNull; - //Stub +public class RecognitionRequest implements Parcelable { protected RecognitionRequest(Parcel in) { } @@ -31,4 +33,28 @@ public int describeContents() { public void writeToParcel(Parcel parcel, int i) { throw new RuntimeException("Stub!"); } + + @NonNull + public AudioAttributes getAudioAttributes() { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AudioFormat getAudioFormat() { + throw new RuntimeException("Stub!"); + } + + public int getCaptureSession() { + throw new RuntimeException("Stub!"); + } + + @SuppressWarnings("MethodNameUnits") + public int getMaxAudioLengthSeconds() { + throw new RuntimeException("Stub!"); + } + + public int getIgnoreBeginningFrames() { + throw new RuntimeException("Stub!"); + } + }