TvInputHardwareManager.java revision d7c29189aa639bfac1e6efcd222e65c2c8ecf3f1
1/* 2 * Copyright (C) 2014 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 17package com.android.server.tv; 18 19import android.content.Context; 20import android.media.AudioDevicePort; 21import android.media.AudioManager; 22import android.media.AudioPatch; 23import android.media.AudioPort; 24import android.media.AudioPortConfig; 25import android.media.tv.ITvInputHardware; 26import android.media.tv.ITvInputHardwareCallback; 27import android.media.tv.TvInputHardwareInfo; 28import android.media.tv.TvStreamConfig; 29import android.os.IBinder; 30import android.os.RemoteException; 31import android.util.Slog; 32import android.util.SparseArray; 33import android.view.KeyEvent; 34import android.view.Surface; 35 36import java.util.ArrayList; 37import java.util.HashSet; 38import java.util.List; 39import java.util.Set; 40 41/** 42 * A helper class for TvInputManagerService to handle TV input hardware. 43 * 44 * This class does a basic connection management and forwarding calls to TvInputHal which eventually 45 * calls to tv_input HAL module. 46 * 47 * @hide 48 */ 49class TvInputHardwareManager implements TvInputHal.Callback { 50 private static final String TAG = TvInputHardwareManager.class.getSimpleName(); 51 private final TvInputHal mHal = new TvInputHal(this); 52 private final SparseArray<Connection> mConnections = new SparseArray<Connection>(); 53 private final List<TvInputHardwareInfo> mInfoList = new ArrayList<TvInputHardwareInfo>(); 54 private final Context mContext; 55 private final Set<Integer> mActiveHdmiSources = new HashSet<Integer>(); 56 private final AudioManager mAudioManager; 57 58 private final Object mLock = new Object(); 59 60 public TvInputHardwareManager(Context context) { 61 mContext = context; 62 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 63 // TODO(hdmi): mHdmiManager = mContext.getSystemService(...); 64 // TODO(hdmi): mHdmiClient = mHdmiManager.getTvClient(); 65 mHal.init(); 66 } 67 68 @Override 69 public void onDeviceAvailable( 70 TvInputHardwareInfo info, TvStreamConfig[] configs) { 71 synchronized (mLock) { 72 Connection connection = new Connection(info); 73 connection.updateConfigsLocked(configs); 74 mConnections.put(info.getDeviceId(), connection); 75 buildInfoListLocked(); 76 // TODO: notify if necessary 77 } 78 } 79 80 private void buildInfoListLocked() { 81 mInfoList.clear(); 82 for (int i = 0; i < mConnections.size(); ++i) { 83 mInfoList.add(mConnections.valueAt(i).getInfoLocked()); 84 } 85 } 86 87 @Override 88 public void onDeviceUnavailable(int deviceId) { 89 synchronized (mLock) { 90 Connection connection = mConnections.get(deviceId); 91 if (connection == null) { 92 Slog.e(TAG, "onDeviceUnavailable: Cannot find a connection with " + deviceId); 93 return; 94 } 95 connection.resetLocked(null, null, null, null); 96 mConnections.remove(deviceId); 97 buildInfoListLocked(); 98 // TODO: notify if necessary 99 } 100 } 101 102 @Override 103 public void onStreamConfigurationChanged(int deviceId, TvStreamConfig[] configs) { 104 synchronized (mLock) { 105 Connection connection = mConnections.get(deviceId); 106 if (connection == null) { 107 Slog.e(TAG, "StreamConfigurationChanged: Cannot find a connection with " 108 + deviceId); 109 return; 110 } 111 connection.updateConfigsLocked(configs); 112 try { 113 connection.getCallbackLocked().onStreamConfigChanged(configs); 114 } catch (RemoteException e) { 115 Slog.e(TAG, "onStreamConfigurationChanged: " + e); 116 } 117 } 118 } 119 120 public List<TvInputHardwareInfo> getHardwareList() { 121 synchronized (mLock) { 122 return mInfoList; 123 } 124 } 125 126 private boolean checkUidChangedLocked( 127 Connection connection, int callingUid, int resolvedUserId) { 128 Integer connectionCallingUid = connection.getCallingUidLocked(); 129 Integer connectionResolvedUserId = connection.getResolvedUserIdLocked(); 130 if (connectionCallingUid == null || connectionResolvedUserId == null) { 131 return true; 132 } 133 if (connectionCallingUid != callingUid || connectionResolvedUserId != resolvedUserId) { 134 return true; 135 } 136 return false; 137 } 138 139 /** 140 * Create a TvInputHardware object with a specific deviceId. One service at a time can access 141 * the object, and if more than one process attempts to create hardware with the same deviceId, 142 * the latest service will get the object and all the other hardware are released. The 143 * release is notified via ITvInputHardwareCallback.onReleased(). 144 */ 145 public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback, 146 int callingUid, int resolvedUserId) { 147 if (callback == null) { 148 throw new NullPointerException(); 149 } 150 synchronized (mLock) { 151 Connection connection = mConnections.get(deviceId); 152 if (connection == null) { 153 Slog.e(TAG, "Invalid deviceId : " + deviceId); 154 return null; 155 } 156 if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) { 157 TvInputHardwareImpl hardware = new TvInputHardwareImpl(connection.getInfoLocked()); 158 try { 159 callback.asBinder().linkToDeath(connection, 0); 160 } catch (RemoteException e) { 161 hardware.release(); 162 return null; 163 } 164 connection.resetLocked(hardware, callback, callingUid, resolvedUserId); 165 } 166 return connection.getHardwareLocked(); 167 } 168 } 169 170 /** 171 * Release the specified hardware. 172 */ 173 public void releaseHardware(int deviceId, ITvInputHardware hardware, int callingUid, 174 int resolvedUserId) { 175 synchronized (mLock) { 176 Connection connection = mConnections.get(deviceId); 177 if (connection == null) { 178 Slog.e(TAG, "Invalid deviceId : " + deviceId); 179 return; 180 } 181 if (connection.getHardwareLocked() != hardware 182 || checkUidChangedLocked(connection, callingUid, resolvedUserId)) { 183 return; 184 } 185 connection.resetLocked(null, null, null, null); 186 } 187 } 188 189 private class Connection implements IBinder.DeathRecipient { 190 private final TvInputHardwareInfo mInfo; 191 private TvInputHardwareImpl mHardware = null; 192 private ITvInputHardwareCallback mCallback; 193 private TvStreamConfig[] mConfigs = null; 194 private Integer mCallingUid = null; 195 private Integer mResolvedUserId = null; 196 197 public Connection(TvInputHardwareInfo info) { 198 mInfo = info; 199 } 200 201 // *Locked methods assume TvInputHardwareManager.mLock is held. 202 203 public void resetLocked(TvInputHardwareImpl hardware, 204 ITvInputHardwareCallback callback, Integer callingUid, Integer resolvedUserId) { 205 if (mHardware != null) { 206 try { 207 mCallback.onReleased(); 208 } catch (RemoteException e) { 209 Slog.e(TAG, "Connection::resetHardware: " + e); 210 } 211 mHardware.release(); 212 } 213 mHardware = hardware; 214 mCallback = callback; 215 mCallingUid = callingUid; 216 mResolvedUserId = resolvedUserId; 217 218 if (mHardware != null && mCallback != null) { 219 try { 220 mCallback.onStreamConfigChanged(getConfigsLocked()); 221 } catch (RemoteException e) { 222 Slog.e(TAG, "Connection::resetHardware: " + e); 223 } 224 } 225 } 226 227 public void updateConfigsLocked(TvStreamConfig[] configs) { 228 mConfigs = configs; 229 } 230 231 public TvInputHardwareInfo getInfoLocked() { 232 return mInfo; 233 } 234 235 public ITvInputHardware getHardwareLocked() { 236 return mHardware; 237 } 238 239 public ITvInputHardwareCallback getCallbackLocked() { 240 return mCallback; 241 } 242 243 public TvStreamConfig[] getConfigsLocked() { 244 return mConfigs; 245 } 246 247 public Integer getCallingUidLocked() { 248 return mCallingUid; 249 } 250 251 public Integer getResolvedUserIdLocked() { 252 return mResolvedUserId; 253 } 254 255 @Override 256 public void binderDied() { 257 synchronized (mLock) { 258 resetLocked(null, null, null, null); 259 } 260 } 261 } 262 263 private class TvInputHardwareImpl extends ITvInputHardware.Stub { 264 private final TvInputHardwareInfo mInfo; 265 private boolean mReleased = false; 266 private final Object mImplLock = new Object(); 267 268 private final AudioDevicePort mAudioSource; 269 private final AudioDevicePort mAudioSink; 270 private AudioPatch mAudioPatch = null; 271 272 public TvInputHardwareImpl(TvInputHardwareInfo info) { 273 mInfo = info; 274 AudioDevicePort audioSource = null; 275 AudioDevicePort audioSink = null; 276 if (mInfo.getAudioType() != AudioManager.DEVICE_NONE) { 277 ArrayList<AudioPort> devicePorts = new ArrayList<AudioPort>(); 278 if (mAudioManager.listAudioDevicePorts(devicePorts) == AudioManager.SUCCESS) { 279 // Find source 280 for (AudioPort port : devicePorts) { 281 AudioDevicePort devicePort = (AudioDevicePort) port; 282 if (devicePort.type() == mInfo.getAudioType() && 283 devicePort.address().equals(mInfo.getAudioAddress())) { 284 audioSource = devicePort; 285 break; 286 } 287 } 288 // Find sink 289 // TODO: App may want to specify sink device? 290 int sinkDevices = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC); 291 for (AudioPort port : devicePorts) { 292 AudioDevicePort devicePort = (AudioDevicePort) port; 293 if (devicePort.type() == sinkDevices) { 294 audioSink = devicePort; 295 break; 296 } 297 } 298 } 299 } 300 mAudioSource = audioSource; 301 mAudioSink = audioSink; 302 } 303 304 public void release() { 305 synchronized (mImplLock) { 306 if (mAudioPatch != null) { 307 mAudioManager.releaseAudioPatch(mAudioPatch); 308 mAudioPatch = null; 309 } 310 mReleased = true; 311 } 312 } 313 314 @Override 315 public boolean setSurface(Surface surface, TvStreamConfig config) 316 throws RemoteException { 317 synchronized (mImplLock) { 318 if (mReleased) { 319 throw new IllegalStateException("Device already released."); 320 } 321 if (mInfo.getType() == TvInputHal.TYPE_HDMI) { 322 if (surface != null) { 323 // Set "Active Source" for HDMI. 324 // TODO(hdmi): mHdmiClient.deviceSelect(...); 325 mActiveHdmiSources.add(mInfo.getDeviceId()); 326 } else { 327 mActiveHdmiSources.remove(mInfo.getDeviceId()); 328 if (mActiveHdmiSources.size() == 0) { 329 // Tell HDMI that no HDMI source is active 330 // TODO(hdmi): mHdmiClient.portSelect(null); 331 } 332 } 333 } 334 if (mAudioSource != null && mAudioSink != null) { 335 if (surface != null) { 336 AudioPortConfig sourceConfig = mAudioSource.activeConfig(); 337 AudioPortConfig sinkConfig = mAudioSink.activeConfig(); 338 AudioPatch[] audioPatchArray = new AudioPatch[] { mAudioPatch }; 339 // TODO: build config if activeConfig() == null 340 mAudioManager.createAudioPatch( 341 audioPatchArray, 342 new AudioPortConfig[] { sourceConfig }, 343 new AudioPortConfig[] { sinkConfig }); 344 mAudioPatch = audioPatchArray[0]; 345 } else { 346 mAudioManager.releaseAudioPatch(mAudioPatch); 347 mAudioPatch = null; 348 } 349 } 350 return mHal.setSurface(mInfo.getDeviceId(), surface, config) == TvInputHal.SUCCESS; 351 } 352 } 353 354 @Override 355 public void setVolume(float volume) throws RemoteException { 356 synchronized (mImplLock) { 357 if (mReleased) { 358 throw new IllegalStateException("Device already released."); 359 } 360 } 361 // TODO: Use AudioGain? 362 } 363 364 @Override 365 public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException { 366 synchronized (mImplLock) { 367 if (mReleased) { 368 throw new IllegalStateException("Device already released."); 369 } 370 } 371 if (mInfo.getType() != TvInputHal.TYPE_HDMI) { 372 return false; 373 } 374 // TODO(hdmi): mHdmiClient.sendKeyEvent(event); 375 return false; 376 } 377 } 378} 379