PageRenderTime 1393ms CodeModel.GetById 2ms RepoModel.GetById 1ms app.codeStats 0ms

/frameworks/base/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java

https://bitbucket.org/nemoid/griffinproject
Java | 441 lines | 288 code | 28 blank | 125 comment | 69 complexity | 4c12666b4e39fcbe5ec355da9c955f9d MD5 | raw file
  1. /*
  2. * Copyright (C) 2008 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. package com.android.systemui.media;
  17. import android.content.Context;
  18. import android.media.AudioAttributes;
  19. import android.media.AudioManager;
  20. import android.media.AudioManager.OnAudioFocusChangeListener;
  21. import android.media.MediaPlayer;
  22. import android.media.MediaPlayer.OnCompletionListener;
  23. import android.media.MediaPlayer.OnErrorListener;
  24. import android.net.Uri;
  25. import android.os.Looper;
  26. import android.os.PowerManager;
  27. import android.os.SystemClock;
  28. import android.telephony.TelephonyManager;
  29. import android.util.Log;
  30. import java.util.LinkedList;
  31. /**
  32. * @hide
  33. * This class is provides the same interface and functionality as android.media.AsyncPlayer
  34. * with the following differences:
  35. * - whenever audio is played, audio focus is requested,
  36. * - whenever audio playback is stopped or the playback completed, audio focus is abandoned.
  37. */
  38. public class NotificationPlayer implements OnCompletionListener, OnErrorListener {
  39. private static final int PLAY = 1;
  40. private static final int STOP = 2;
  41. private static final boolean mDebug = false;
  42. private static final class Command {
  43. int code;
  44. Context context;
  45. Uri uri;
  46. boolean looping;
  47. AudioAttributes attributes;
  48. long requestTime;
  49. public String toString() {
  50. return "{ code=" + code + " looping=" + looping + " attributes=" + attributes
  51. + " uri=" + uri + " }";
  52. }
  53. }
  54. private LinkedList<Command> mCmdQueue = new LinkedList();
  55. private Looper mLooper;
  56. /**
  57. * SPRD:Bug598291 Ringtone rings while notification is ringing.
  58. * @{
  59. */
  60. OnAudioFocusChangeListener mAudioFocuslistener = new OnAudioFocusChangeListener() {
  61. public void onAudioFocusChange(int focusChange) {
  62. Log.d(mTag, "onAudioFocusChange focusChange="+focusChange);
  63. if (focusChange < 0) {
  64. Log.d(mTag, "audio lose focus, stop");
  65. stop();
  66. }
  67. }
  68. };
  69. /**
  70. * @}
  71. */
  72. /*
  73. * Besides the use of audio focus, the only implementation difference between AsyncPlayer and
  74. * NotificationPlayer resides in the creation of the MediaPlayer. For the completion callback,
  75. * OnCompletionListener, to be called at the end of the playback, the MediaPlayer needs to
  76. * be created with a looper running so its event handler is not null.
  77. */
  78. private final class CreationAndCompletionThread extends Thread {
  79. public Command mCmd;
  80. public CreationAndCompletionThread(Command cmd) {
  81. super();
  82. mCmd = cmd;
  83. }
  84. public void run() {
  85. Looper.prepare();
  86. mLooper = Looper.myLooper();
  87. synchronized(this) {
  88. AudioManager audioManager =
  89. (AudioManager) mCmd.context.getSystemService(Context.AUDIO_SERVICE);
  90. try {
  91. MediaPlayer player = new MediaPlayer();
  92. player.setAudioAttributes(mCmd.attributes);
  93. player.setDataSource(mCmd.context, mCmd.uri);
  94. player.setLooping(mCmd.looping);
  95. player.prepare();
  96. /* SPRD 606596 @{*/
  97. int requestResult = 0;
  98. /* @} */
  99. if ((mCmd.uri != null) && (mCmd.uri.getEncodedPath() != null)
  100. && (mCmd.uri.getEncodedPath().length() > 0)) {
  101. if (!audioManager.isMusicActiveRemotely()) {
  102. synchronized(mQueueAudioFocusLock) {
  103. //SPRD bug 609235 :Can't play the long ring while received the second SMS.{@
  104. /*SPRD bug 615659:Rollback this code{@*/
  105. if (mAudioManagerWithAudioFocus == null) {
  106. //@}
  107. if (mDebug) Log.d(mTag, "requesting AudioFocus mCmd.looping="+mCmd.looping);
  108. if (mCmd.looping) {
  109. requestResult = audioManager.requestAudioFocus(mAudioFocuslistener,
  110. AudioAttributes.toLegacyStreamType(mCmd.attributes),
  111. AudioManager.AUDIOFOCUS_GAIN);
  112. } else {
  113. requestResult = audioManager.requestAudioFocus(mAudioFocuslistener,
  114. AudioAttributes.toLegacyStreamType(mCmd.attributes),
  115. AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
  116. }
  117. /*SPRD bug 615659:Rollback this code{@*/
  118. /* SPRD 606596 @{*/
  119. if (requestResult != 0){
  120. // mAudioManagerWithAudioFocus = audioManager;
  121. }else{
  122. //SPRD bug 609235 :Can't play the long ring while received the second SMS.{@
  123. Log.d(mTag, "AudioFocus was not gain: requestResult"+requestResult);
  124. }
  125. /* @} */
  126. /*SPRD bug 615659:Rollback this code{@*/
  127. mAudioManagerWithAudioFocus = audioManager;
  128. } else {
  129. if (mDebug) Log.d(mTag, "AudioFocus was previously requested");
  130. }
  131. }
  132. }
  133. }
  134. if (mDebug) Log.d(mTag, "requesting AudioFocus requestResult="+requestResult);
  135. // FIXME Having to start a new thread so we can receive completion callbacks
  136. // is wrong, as we kill this thread whenever a new sound is to be played. This
  137. // can lead to AudioFocus being released too early, before the second sound is
  138. // done playing. This class should be modified to use a single thread, on which
  139. // command are issued, and on which it receives the completion callbacks.
  140. player.setOnCompletionListener(NotificationPlayer.this);
  141. player.setOnErrorListener(NotificationPlayer.this);
  142. /* SPRD 606596 @{*/
  143. /*SPRD bug 615659:Rollback this code FIXME:Check MODE_RINGTONE for phone ringing{@*/
  144. if (mDebug) Log.d(mTag, "audioManager.getMode()="+audioManager.getMode());
  145. if (/*requestResult != 0*/audioManager.getMode() != AudioManager.MODE_RINGTONE){
  146. player.start();
  147. }
  148. /* @} */
  149. if (mPlayer != null) {
  150. mPlayer.release();
  151. }
  152. mPlayer = player;
  153. }
  154. catch (Exception e) {
  155. Log.w(mTag, "error loading sound for " + mCmd.uri, e);
  156. }
  157. this.notify();
  158. }
  159. Looper.loop();
  160. }
  161. };
  162. private void startSound(Command cmd) {
  163. // Preparing can be slow, so if there is something else
  164. // is playing, let it continue until we're done, so there
  165. // is less of a glitch.
  166. try {
  167. if (mDebug) Log.d(mTag, "Starting playback");
  168. //-----------------------------------
  169. // This is were we deviate from the AsyncPlayer implementation and create the
  170. // MediaPlayer in a new thread with which we're synchronized
  171. synchronized(mCompletionHandlingLock) {
  172. // if another sound was already playing, it doesn't matter we won't get notified
  173. // of the completion, since only the completion notification of the last sound
  174. // matters
  175. if((mLooper != null)
  176. && (mLooper.getThread().getState() != Thread.State.TERMINATED)) {
  177. mLooper.quit();
  178. }
  179. mCompletionThread = new CreationAndCompletionThread(cmd);
  180. synchronized(mCompletionThread) {
  181. mCompletionThread.start();
  182. mCompletionThread.wait();
  183. }
  184. }
  185. //-----------------------------------
  186. long delay = SystemClock.uptimeMillis() - cmd.requestTime;
  187. if (delay > 1000) {
  188. Log.w(mTag, "Notification sound delayed by " + delay + "msecs");
  189. }
  190. }
  191. catch (Exception e) {
  192. Log.w(mTag, "error loading sound for " + cmd.uri, e);
  193. }
  194. }
  195. private final class CmdThread extends java.lang.Thread {
  196. CmdThread() {
  197. super("NotificationPlayer-" + mTag);
  198. }
  199. public void run() {
  200. while (true) {
  201. Command cmd = null;
  202. synchronized (mCmdQueue) {
  203. if (mDebug) Log.d(mTag, "RemoveFirst");
  204. cmd = mCmdQueue.removeFirst();
  205. }
  206. switch (cmd.code) {
  207. case PLAY:
  208. if (mDebug) Log.d(mTag, "PLAY");
  209. startSound(cmd);
  210. break;
  211. case STOP:
  212. if (mDebug) Log.d(mTag, "STOP");
  213. if (mPlayer != null) {
  214. long delay = SystemClock.uptimeMillis() - cmd.requestTime;
  215. if (delay > 1000) {
  216. Log.w(mTag, "Notification stop delayed by " + delay + "msecs");
  217. }
  218. mPlayer.stop();
  219. mPlayer.release();
  220. mPlayer = null;
  221. synchronized(mQueueAudioFocusLock) {
  222. if (mAudioManagerWithAudioFocus != null) {
  223. mAudioManagerWithAudioFocus.abandonAudioFocus(mAudioFocuslistener);
  224. mAudioManagerWithAudioFocus = null;
  225. }
  226. }
  227. if((mLooper != null)
  228. && (mLooper.getThread().getState() != Thread.State.TERMINATED)) {
  229. mLooper.quit();
  230. }
  231. } else {
  232. Log.w(mTag, "STOP command without a player");
  233. }
  234. break;
  235. }
  236. synchronized (mCmdQueue) {
  237. if (mCmdQueue.size() == 0) {
  238. // nothing left to do, quit
  239. // doing this check after we're done prevents the case where they
  240. // added it during the operation from spawning two threads and
  241. // trying to do them in parallel.
  242. mThread = null;
  243. releaseWakeLock();
  244. return;
  245. }
  246. }
  247. }
  248. }
  249. }
  250. public void onCompletion(MediaPlayer mp) {
  251. synchronized(mQueueAudioFocusLock) {
  252. if (mAudioManagerWithAudioFocus != null) {
  253. if (mDebug) Log.d(mTag, "onCompletion() abandonning AudioFocus");
  254. mAudioManagerWithAudioFocus.abandonAudioFocus(mAudioFocuslistener);
  255. mAudioManagerWithAudioFocus = null;
  256. } else {
  257. if (mDebug) Log.d(mTag, "onCompletion() no need to abandon AudioFocus");
  258. }
  259. }
  260. // if there are no more sounds to play, end the Looper to listen for media completion
  261. synchronized (mCmdQueue) {
  262. if (mCmdQueue.size() == 0) {
  263. synchronized(mCompletionHandlingLock) {
  264. if(mLooper != null) {
  265. mLooper.quit();
  266. }
  267. mCompletionThread = null;
  268. }
  269. }
  270. }
  271. }
  272. public boolean onError(MediaPlayer mp, int what, int extra) {
  273. Log.e(mTag, "error " + what + " (extra=" + extra + ") playing notification");
  274. // error happened, handle it just like a completion
  275. onCompletion(mp);
  276. return true;
  277. }
  278. private String mTag;
  279. private CmdThread mThread;
  280. private CreationAndCompletionThread mCompletionThread;
  281. private final Object mCompletionHandlingLock = new Object();
  282. private MediaPlayer mPlayer;
  283. private PowerManager.WakeLock mWakeLock;
  284. private final Object mQueueAudioFocusLock = new Object();
  285. private AudioManager mAudioManagerWithAudioFocus; // synchronized on mQueueAudioFocusLock
  286. // The current state according to the caller. Reality lags behind
  287. // because of the asynchronous nature of this class.
  288. private int mState = STOP;
  289. /**
  290. * Construct a NotificationPlayer object.
  291. *
  292. * @param tag a string to use for debugging
  293. */
  294. public NotificationPlayer(String tag) {
  295. if (tag != null) {
  296. mTag = tag;
  297. } else {
  298. mTag = "NotificationPlayer";
  299. }
  300. }
  301. /**
  302. * Start playing the sound. It will actually start playing at some
  303. * point in the future. There are no guarantees about latency here.
  304. * Calling this before another audio file is done playing will stop
  305. * that one and start the new one.
  306. *
  307. * @param context Your application's context.
  308. * @param uri The URI to play. (see {@link MediaPlayer#setDataSource(Context, Uri)})
  309. * @param looping Whether the audio should loop forever.
  310. * (see {@link MediaPlayer#setLooping(boolean)})
  311. * @param stream the AudioStream to use.
  312. * (see {@link MediaPlayer#setAudioStreamType(int)})
  313. * @deprecated use {@link #play(Context, Uri, boolean, AudioAttributes)} instead.
  314. */
  315. @Deprecated
  316. public void play(Context context, Uri uri, boolean looping, int stream) {
  317. Command cmd = new Command();
  318. cmd.requestTime = SystemClock.uptimeMillis();
  319. cmd.code = PLAY;
  320. cmd.context = context;
  321. cmd.uri = uri;
  322. cmd.looping = looping;
  323. cmd.attributes = new AudioAttributes.Builder().setInternalLegacyStreamType(stream).build();
  324. synchronized (mCmdQueue) {
  325. enqueueLocked(cmd);
  326. mState = PLAY;
  327. }
  328. }
  329. /**
  330. * Start playing the sound. It will actually start playing at some
  331. * point in the future. There are no guarantees about latency here.
  332. * Calling this before another audio file is done playing will stop
  333. * that one and start the new one.
  334. *
  335. * @param context Your application's context.
  336. * @param uri The URI to play. (see {@link MediaPlayer#setDataSource(Context, Uri)})
  337. * @param looping Whether the audio should loop forever.
  338. * (see {@link MediaPlayer#setLooping(boolean)})
  339. * @param attributes the AudioAttributes to use.
  340. * (see {@link MediaPlayer#setAudioAttributes(AudioAttributes)})
  341. */
  342. public void play(Context context, Uri uri, boolean looping, AudioAttributes attributes) {
  343. Command cmd = new Command();
  344. cmd.requestTime = SystemClock.uptimeMillis();
  345. cmd.code = PLAY;
  346. cmd.context = context;
  347. cmd.uri = uri;
  348. cmd.looping = looping;
  349. cmd.attributes = attributes;
  350. synchronized (mCmdQueue) {
  351. enqueueLocked(cmd);
  352. mState = PLAY;
  353. }
  354. }
  355. /**
  356. * Stop a previously played sound. It can't be played again or unpaused
  357. * at this point. Calling this multiple times has no ill effects.
  358. */
  359. public void stop() {
  360. synchronized (mCmdQueue) {
  361. // This check allows stop to be called multiple times without starting
  362. // a thread that ends up doing nothing.
  363. if (mState != STOP) {
  364. Command cmd = new Command();
  365. cmd.requestTime = SystemClock.uptimeMillis();
  366. cmd.code = STOP;
  367. enqueueLocked(cmd);
  368. mState = STOP;
  369. }
  370. }
  371. }
  372. private void enqueueLocked(Command cmd) {
  373. mCmdQueue.add(cmd);
  374. if (mThread == null) {
  375. acquireWakeLock();
  376. mThread = new CmdThread();
  377. mThread.start();
  378. }
  379. }
  380. /**
  381. * We want to hold a wake lock while we do the prepare and play. The stop probably is
  382. * optional, but it won't hurt to have it too. The problem is that if you start a sound
  383. * while you're holding a wake lock (e.g. an alarm starting a notification), you want the
  384. * sound to play, but if the CPU turns off before mThread gets to work, it won't. The
  385. * simplest way to deal with this is to make it so there is a wake lock held while the
  386. * thread is starting or running. You're going to need the WAKE_LOCK permission if you're
  387. * going to call this.
  388. *
  389. * This must be called before the first time play is called.
  390. *
  391. * @hide
  392. */
  393. public void setUsesWakeLock(Context context) {
  394. if (mWakeLock != null || mThread != null) {
  395. // if either of these has happened, we've already played something.
  396. // and our releases will be out of sync.
  397. throw new RuntimeException("assertion failed mWakeLock=" + mWakeLock
  398. + " mThread=" + mThread);
  399. }
  400. PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
  401. mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mTag);
  402. }
  403. private void acquireWakeLock() {
  404. if (mWakeLock != null) {
  405. mWakeLock.acquire();
  406. }
  407. }
  408. private void releaseWakeLock() {
  409. if (mWakeLock != null) {
  410. mWakeLock.release();
  411. }
  412. }
  413. }