PageRenderTime 108ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

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

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