1 /*
2  * Copyright (C) 2018 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 com.android.midi;
18 
19 import android.content.Context;
20 import android.content.Intent;
21 import android.media.midi.MidiDeviceInfo;
22 import android.media.midi.MidiDeviceService;
23 import android.media.midi.MidiDeviceStatus;
24 import android.media.midi.MidiManager;
25 import android.media.midi.MidiReceiver;
26 import android.os.Bundle;
27 import android.os.IBinder;
28 import android.util.Log;
29 
30 import java.io.IOException;
31 import java.util.Collection;
32 
33 /**
34  * Virtual MIDI Device that copies its input to its output.
35  * This is used for loop-back testing of MIDI I/O.
36  *
37  * Note: The application's AndroidManifest.xml should contain the following in
38  * its <application> section.
39  *
40      <service android:name="MidiEchoTestService"
41          android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE">
42          <intent-filter>
43              <action android:name="android.media.midi.MidiDeviceService" />
44          </intent-filter>
45          <meta-data android:name="android.media.midi.MidiDeviceService"
46             android:resource="@xml/echo_device_info" />
47      </service>
48 
49  * also it must provide an xml reource file "echo_device_info.xml" containing:
50      <devices>
51          <device manufacturer="AndroidCTS" product="MidiEcho" tags="echo,test">
52              <input-port name="input" />
53              <output-port name="output" />
54          </device>
55      </devices>
56  */
57 
58 public class MidiEchoTestService extends MidiDeviceService {
59     private static final String TAG = "MidiEchoTestService";
60     private static final boolean DEBUG = false;
61 
62     // Other apps will write to this port.
63     private MidiReceiver mInputReceiver = new MyReceiver();
64     // This app will copy the data to this port.
65     private MidiReceiver mOutputReceiver;
66     private static MidiEchoTestService sInstance;
67 
68     // These are public so we can easily read them from CTS test.
69     public int statusChangeCount;
70     public boolean inputOpened;
71     public int outputOpenCount;
72 
73     public static final String TEST_MANUFACTURER = "AndroidCTS";
74     public static final String ECHO_PRODUCT = "MidiEcho";
75 
76     /**
77      * Search through the available devices for the ECHO loop-back device.
78      */
findEchoDevice(Context context)79     public static MidiDeviceInfo findEchoDevice(Context context) {
80         MidiManager midiManager =
81                 (MidiManager) context.getSystemService(Context.MIDI_SERVICE);
82         Collection<MidiDeviceInfo> infos = midiManager.getDevicesForTransport(
83                 MidiManager.TRANSPORT_MIDI_BYTE_STREAM);
84         MidiDeviceInfo echoInfo = null;
85         for (MidiDeviceInfo info : infos) {
86             Bundle properties = info.getProperties();
87             String manufacturer = properties.getString(
88                     MidiDeviceInfo.PROPERTY_MANUFACTURER);
89 
90             if (TEST_MANUFACTURER.equals(manufacturer)) {
91                 String product = properties.getString(
92                         MidiDeviceInfo.PROPERTY_PRODUCT);
93                 if (ECHO_PRODUCT.equals(product)) {
94                     echoInfo = info;
95                     break;
96                 }
97             }
98         }
99         if (DEBUG) {
100             Log.i(TAG, "MidiEchoService for " + ECHO_PRODUCT + ": " + echoInfo);
101         }
102         return echoInfo;
103     }
104 
105     /**
106      * @return A textual name for this echo service.
107      */
getEchoServerName()108     public static String getEchoServerName() {
109         return ECHO_PRODUCT;
110     }
111 
112     @Override
onCreate()113     public void onCreate() {
114         super.onCreate();
115         if (DEBUG) {
116             Log.i(TAG, "#### onCreate()");
117         }
118         sInstance = this;
119     }
120 
121     @Override
onDestroy()122     public void onDestroy() {
123         super.onDestroy();
124         if (DEBUG) {
125             Log.i(TAG, "#### onDestroy()");
126         }
127     }
128 
129     @Override
onStartCommand(Intent intent, int flags, int startId)130     public int onStartCommand(Intent intent, int flags, int startId) {
131         if (DEBUG) {
132             Log.i(TAG, "#### onStartCommand()");
133         }
134         return super.onStartCommand(intent, flags, startId);
135     }
136 
137     @Override
onBind(Intent intent)138     public IBinder onBind(Intent intent) {
139         if (DEBUG) {
140             Log.i(TAG, "#### onBind()");
141         }
142         return super.onBind(intent);
143     }
144 
145     // For CTS testing, so I can read test fields.
getInstance()146     public static MidiEchoTestService getInstance() {
147         return sInstance;
148     }
149 
150     @Override
onGetInputPortReceivers()151     public MidiReceiver[] onGetInputPortReceivers() {
152         return new MidiReceiver[] { mInputReceiver };
153     }
154 
155     class MyReceiver extends MidiReceiver {
156         @Override
onSend(byte[] data, int offset, int count, long timestamp)157         public void onSend(byte[] data, int offset, int count, long timestamp)
158                 throws IOException {
159             if (mOutputReceiver == null) {
160                 mOutputReceiver = getOutputPortReceivers()[0];
161             }
162             // Copy input to output.
163             mOutputReceiver.send(data, offset, count, timestamp);
164         }
165     }
166 
167     @Override
onDeviceStatusChanged(MidiDeviceStatus status)168     public void onDeviceStatusChanged(MidiDeviceStatus status) {
169         statusChangeCount++;
170         inputOpened = status.isInputPortOpen(0);
171         outputOpenCount = status.getOutputPortOpenCount(0);
172     }
173 }
174