1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.media.musicrecognition; 18 19 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.annotation.SuppressLint; 24 import android.annotation.SystemApi; 25 import android.app.Service; 26 import android.content.Intent; 27 import android.media.AudioFormat; 28 import android.media.MediaMetadata; 29 import android.os.Bundle; 30 import android.os.Handler; 31 import android.os.IBinder; 32 import android.os.Looper; 33 import android.os.ParcelFileDescriptor; 34 import android.os.RemoteException; 35 import android.util.Log; 36 37 /** 38 * Implemented by an app that wants to offer music search lookups. The system will start the 39 * service and stream up to 16 seconds of audio over the given file descriptor. 40 * 41 * @hide 42 */ 43 @SystemApi 44 public abstract class MusicRecognitionService extends Service { 45 46 private static final String TAG = MusicRecognitionService.class.getSimpleName(); 47 48 /** Callback for the result of the remote search. */ 49 public interface Callback { 50 /** 51 * Call this method to pass back a successful search result. 52 * 53 * @param result successful result of the search 54 * @param extras extra data to be supplied back to the caller. Note that all executable 55 * parameters and file descriptors would be removed from the supplied bundle 56 */ onRecognitionSucceeded(@onNull MediaMetadata result, @SuppressLint(R) @Nullable Bundle extras)57 void onRecognitionSucceeded(@NonNull MediaMetadata result, 58 @SuppressLint("NullableCollection") 59 @Nullable Bundle extras); 60 61 /** 62 * Call this method if the search does not find a result on an error occurred. 63 */ onRecognitionFailed(@usicRecognitionManager.RecognitionFailureCode int failureCode)64 void onRecognitionFailed(@MusicRecognitionManager.RecognitionFailureCode int failureCode); 65 } 66 67 /** 68 * Action used to start this service. 69 * 70 * @hide 71 */ 72 public static final String ACTION_MUSIC_SEARCH_LOOKUP = 73 "android.service.musicrecognition.MUSIC_RECOGNITION"; 74 75 private Handler mHandler; 76 private final IMusicRecognitionService mServiceInterface = 77 new IMusicRecognitionService.Stub() { 78 @Override 79 public void onAudioStreamStarted(ParcelFileDescriptor fd, 80 AudioFormat audioFormat, 81 IMusicRecognitionServiceCallback callback) { 82 mHandler.sendMessage( 83 obtainMessage(MusicRecognitionService.this::onRecognize, fd, 84 audioFormat, 85 new Callback() { 86 @Override 87 public void onRecognitionSucceeded( 88 @NonNull MediaMetadata result, 89 @Nullable Bundle extras) { 90 try { 91 callback.onRecognitionSucceeded(result, extras); 92 } catch (RemoteException e) { 93 throw e.rethrowFromSystemServer(); 94 } 95 } 96 97 @Override 98 public void onRecognitionFailed(int failureCode) { 99 try { 100 callback.onRecognitionFailed(failureCode); 101 } catch (RemoteException e) { 102 throw e.rethrowFromSystemServer(); 103 } 104 } 105 })); 106 } 107 108 @Override 109 public void getAttributionTag( 110 IMusicRecognitionAttributionTagCallback callback) throws RemoteException { 111 String tag = MusicRecognitionService.this.getAttributionTag(); 112 callback.onAttributionTag(tag); 113 } 114 }; 115 116 @Override onCreate()117 public void onCreate() { 118 super.onCreate(); 119 mHandler = new Handler(Looper.getMainLooper(), null, true); 120 } 121 122 /** 123 * Read audio from this stream. You must invoke the callback whether the music is recognized or 124 * not. 125 * 126 * @param stream containing music to be recognized. Close when you are finished. 127 * @param audioFormat describes sample rate, channels and endianness of the stream 128 * @param callback to invoke after lookup is finished. Must always be called. 129 */ onRecognize(@onNull ParcelFileDescriptor stream, @NonNull AudioFormat audioFormat, @NonNull Callback callback)130 public abstract void onRecognize(@NonNull ParcelFileDescriptor stream, 131 @NonNull AudioFormat audioFormat, 132 @NonNull Callback callback); 133 134 /** 135 * @hide 136 */ 137 @Nullable 138 @Override onBind(@onNull Intent intent)139 public IBinder onBind(@NonNull Intent intent) { 140 if (ACTION_MUSIC_SEARCH_LOOKUP.equals(intent.getAction())) { 141 return mServiceInterface.asBinder(); 142 } 143 Log.w(TAG, 144 "Tried to bind to wrong intent (should be " + ACTION_MUSIC_SEARCH_LOOKUP + ": " 145 + intent); 146 return null; 147 } 148 } 149