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.musicrecognition.cts;
18 
19 import static com.google.common.truth.Truth.assertWithMessage;
20 
21 import android.content.ComponentName;
22 import android.media.AudioFormat;
23 import android.media.MediaMetadata;
24 import android.media.musicrecognition.MusicRecognitionService;
25 import android.os.Bundle;
26 import android.os.ParcelFileDescriptor;
27 import android.util.Log;
28 
29 import androidx.annotation.NonNull;
30 import androidx.annotation.Nullable;
31 
32 import com.google.common.io.ByteStreams;
33 
34 import java.io.IOException;
35 import java.util.concurrent.CountDownLatch;
36 import java.util.concurrent.TimeUnit;
37 
38 /** No-op implementation of MusicRecognitionService for testing purposes. */
39 public class CtsMusicRecognitionService extends MusicRecognitionService {
40     private static final String TAG = CtsMusicRecognitionService.class.getSimpleName();
41     public static final String SERVICE_PACKAGE = "android.musicrecognition.cts";
42     public static final ComponentName SERVICE_COMPONENT = new ComponentName(
43             SERVICE_PACKAGE, CtsMusicRecognitionService.class.getName());
44 
45     private static Watcher sWatcher;
46 
47     @Override
onDestroy()48     public void onDestroy() {
49         super.onDestroy();
50         sWatcher.destroyed.countDown();
51     }
52 
53     @Override
onRecognize(@onNull ParcelFileDescriptor stream, @NonNull AudioFormat audioFormat, @NonNull Callback callback)54     public void onRecognize(@NonNull ParcelFileDescriptor stream,
55             @NonNull AudioFormat audioFormat,
56             @NonNull Callback callback) {
57         if (sWatcher.failureCode != 0) {
58             callback.onRecognitionFailed(sWatcher.failureCode);
59         } else {
60             Log.i(TAG, "Reading audio stream...");
61             sWatcher.stream = readStream(stream);
62             Log.i(TAG, "Reading audio done.");
63             callback.onRecognitionSucceeded(sWatcher.result, sWatcher.resultExtras);
64         }
65     }
66 
67     @Override
getAttributionTag()68     public @Nullable String getAttributionTag() {
69         return "CtsMusicRecognitionAttributionTag";
70     }
71 
readStream(ParcelFileDescriptor stream)72     private byte[] readStream(ParcelFileDescriptor stream) {
73         ParcelFileDescriptor.AutoCloseInputStream fis =
74                 new ParcelFileDescriptor.AutoCloseInputStream(stream);
75         try {
76             return ByteStreams.toByteArray(fis);
77         } catch (IOException e) {
78             throw new RuntimeException(e);
79         }
80     }
81 
setWatcher()82     public static Watcher setWatcher() {
83         if (sWatcher != null) {
84             throw new IllegalStateException("Set watcher with watcher already set");
85         }
86         sWatcher = new Watcher();
87         return sWatcher;
88     }
89 
clearWatcher()90     public static void clearWatcher() {
91         sWatcher = null;
92     }
93 
94     public static final class Watcher {
95         private static final long SERVICE_LIFECYCLE_TIMEOUT_MS = 30_000;
96 
97         public CountDownLatch destroyed = new CountDownLatch(1);
98         public int failureCode = 0;
99         public byte[] stream;
100         public MediaMetadata result;
101         public Bundle resultExtras;
102 
awaitOnDestroy()103         public void awaitOnDestroy() {
104             await(destroyed, "Waiting for service destroyed");
105         }
106 
await(@onNull CountDownLatch latch, @NonNull String message)107         private void await(@NonNull CountDownLatch latch, @NonNull String message) {
108             try {
109                 assertWithMessage(message).that(
110                         latch.await(SERVICE_LIFECYCLE_TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
111             } catch (InterruptedException e) {
112                 Thread.currentThread().interrupt();
113                 throw new IllegalStateException("Interrupted while: " + message);
114             }
115         }
116     }
117 }
118