PageRenderTime 67ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/src/com/android/messaging/util/NotificationPlayer.java

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