PageRenderTime 42ms CodeModel.GetById 36ms RepoModel.GetById 0ms app.codeStats 1ms

/services/java/com/android/server/NotificationPlayer.java

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