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