1 /* 2 * Copyright (C) 2015 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.example.android.common.midi; 18 19 import android.app.Activity; 20 import android.media.midi.MidiDeviceInfo; 21 import android.media.midi.MidiDeviceStatus; 22 import android.media.midi.MidiManager; 23 import android.media.midi.MidiManager.DeviceCallback; 24 import android.os.Handler; 25 import android.os.Looper; 26 import android.util.Log; 27 import android.view.View; 28 import android.widget.AdapterView; 29 import android.widget.ArrayAdapter; 30 import android.widget.Spinner; 31 32 import java.util.HashSet; 33 34 /** 35 * Base class that uses a Spinner to select available MIDI ports. 36 */ 37 public abstract class MidiPortSelector extends DeviceCallback { 38 private int mType = MidiDeviceInfo.PortInfo.TYPE_INPUT; 39 protected ArrayAdapter<MidiPortWrapper> mAdapter; 40 protected HashSet<MidiPortWrapper> mBusyPorts = new HashSet<MidiPortWrapper>(); 41 private Spinner mSpinner; 42 protected MidiManager mMidiManager; 43 protected Activity mActivity; 44 private MidiPortWrapper mCurrentWrapper; 45 46 /** 47 * @param midiManager 48 * @param activity 49 * @param spinnerId 50 * ID from the layout resource 51 * @param type 52 * TYPE_INPUT or TYPE_OUTPUT 53 */ 54 public MidiPortSelector(MidiManager midiManager, Activity activity, 55 int spinnerId, int type) { 56 mMidiManager = midiManager; 57 mActivity = activity; 58 mType = type; 59 mAdapter = new ArrayAdapter<MidiPortWrapper>(activity, 60 android.R.layout.simple_spinner_item); 61 mAdapter.setDropDownViewResource( 62 android.R.layout.simple_spinner_dropdown_item); 63 mAdapter.add(new MidiPortWrapper(null, 0, 0)); 64 65 mSpinner = (Spinner) activity.findViewById(spinnerId); 66 mSpinner.setOnItemSelectedListener( 67 new AdapterView.OnItemSelectedListener() { 68 69 public void onItemSelected(AdapterView<?> parent, View view, 70 int pos, long id) { 71 mCurrentWrapper = mAdapter.getItem(pos); 72 onPortSelected(mCurrentWrapper); 73 } 74 75 public void onNothingSelected(AdapterView<?> parent) { 76 onPortSelected(null); 77 mCurrentWrapper = null; 78 } 79 }); 80 mSpinner.setAdapter(mAdapter); 81 82 mMidiManager.registerDeviceCallback(this, 83 new Handler(Looper.getMainLooper())); 84 85 MidiDeviceInfo[] infos = mMidiManager.getDevices(); 86 for (MidiDeviceInfo info : infos) { 87 onDeviceAdded(info); 88 } 89 } 90 91 /** 92 * Set to no port selected. 93 */ 94 public void clearSelection() { 95 mSpinner.setSelection(0); 96 } 97 98 private int getInfoPortCount(final MidiDeviceInfo info) { 99 int portCount = (mType == MidiDeviceInfo.PortInfo.TYPE_INPUT) 100 ? info.getInputPortCount() : info.getOutputPortCount(); 101 return portCount; 102 } 103 104 @Override 105 public void onDeviceAdded(final MidiDeviceInfo info) { 106 int portCount = getInfoPortCount(info); 107 for (int i = 0; i < portCount; ++i) { 108 MidiPortWrapper wrapper = new MidiPortWrapper(info, mType, i); 109 mAdapter.add(wrapper); 110 Log.i(MidiConstants.TAG, wrapper + " was added"); 111 mAdapter.notifyDataSetChanged(); 112 } 113 } 114 115 @Override 116 public void onDeviceRemoved(final MidiDeviceInfo info) { 117 int portCount = getInfoPortCount(info); 118 for (int i = 0; i < portCount; ++i) { 119 MidiPortWrapper wrapper = new MidiPortWrapper(info, mType, i); 120 MidiPortWrapper currentWrapper = mCurrentWrapper; 121 mAdapter.remove(wrapper); 122 // If the currently selected port was removed then select no port. 123 if (wrapper.equals(currentWrapper)) { 124 clearSelection(); 125 } 126 mAdapter.notifyDataSetChanged(); 127 Log.i(MidiConstants.TAG, wrapper + " was removed"); 128 } 129 } 130 131 @Override 132 public void onDeviceStatusChanged(final MidiDeviceStatus status) { 133 // If an input port becomes busy then remove it from the menu. 134 // If it becomes free then add it back to the menu. 135 if (mType == MidiDeviceInfo.PortInfo.TYPE_INPUT) { 136 MidiDeviceInfo info = status.getDeviceInfo(); 137 Log.i(MidiConstants.TAG, "MidiPortSelector.onDeviceStatusChanged status = " + status 138 + ", mType = " + mType 139 + ", activity = " + mActivity.getPackageName() 140 + ", info = " + info); 141 // Look for transitions from free to busy. 142 int portCount = info.getInputPortCount(); 143 for (int i = 0; i < portCount; ++i) { 144 MidiPortWrapper wrapper = new MidiPortWrapper(info, mType, i); 145 if (!wrapper.equals(mCurrentWrapper)) { 146 if (status.isInputPortOpen(i)) { // busy? 147 if (!mBusyPorts.contains(wrapper)) { 148 // was free, now busy 149 mBusyPorts.add(wrapper); 150 mAdapter.remove(wrapper); 151 mAdapter.notifyDataSetChanged(); 152 } 153 } else { 154 if (mBusyPorts.remove(wrapper)) { 155 // was busy, now free 156 mAdapter.add(wrapper); 157 mAdapter.notifyDataSetChanged(); 158 } 159 } 160 } 161 } 162 } 163 } 164 165 /** 166 * Implement this method to handle the user selecting a port on a device. 167 * 168 * @param wrapper 169 */ 170 public abstract void onPortSelected(MidiPortWrapper wrapper); 171 172 /** 173 * Implement this method to clean up any open resources. 174 */ 175 public abstract void onClose(); 176 177 /** 178 * 179 */ 180 public void close() { 181 onClose(); 182 } 183 } 184