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