View Javadoc

1   /***
2    *     Ambient - A music player for the Android platform
3    Copyright (C) 2007 Martin Vysny
4    
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9    
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14  
15   You should have received a copy of the GNU General Public License
16   along with this program.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package sk.baka.ambient;
20  
21  import sk.baka.ambient.collection.TrackMetadataBean;
22  import sk.baka.ambient.commons.Interval;
23  import sk.baka.ambient.commons.ThreadUnsafe;
24  import sk.baka.ambient.playerservice.PlayerStateEnum;
25  import sk.baka.ambient.playlist.PlaylistItem;
26  import sk.baka.ambient.playlist.Random;
27  import sk.baka.ambient.playlist.Repeat;
28  import android.content.BroadcastReceiver;
29  import android.content.Context;
30  import android.content.Intent;
31  import android.content.IntentFilter;
32  import android.net.ConnectivityManager;
33  import android.net.NetworkInfo;
34  import android.net.wifi.WifiManager;
35  import android.net.wifi.WifiManager.WifiLock;
36  import android.util.Log;
37  
38  /***
39   * Ensures that the WiFi is not turned off while playing an online content.
40   * Moreover, monitors the network status info and fires online/offline events.
41   * Initially, the handler expects the phone to be offline - if not, an event is
42   * fired.
43   * <p/>
44   * Thread-unsafe, should be invoked from the main Handler instance only.
45   * 
46   * @author Martin Vysny
47   */
48  @ThreadUnsafe
49  final class NetworkHandler extends BroadcastReceiver implements
50  		IPlaylistPlayerListener {
51  
52  	private final AmbientApplication app;
53  
54  	/***
55  	 * Creates new power handler.
56  	 * 
57  	 * @param app
58  	 *            owner application.
59  	 */
60  	public NetworkHandler(final AmbientApplication app) {
61  		this.app = app;
62  		app.getBus().addHandler(this);
63  		app.registerReceiver(this, new IntentFilter(
64  				ConnectivityManager.CONNECTIVITY_ACTION));
65  		onNetworkStateChanged(computeOffline());
66  	}
67  
68  	public void playbackStateChanged(PlayerStateEnum state) {
69  		if (state == PlayerStateEnum.Stopped) {
70  			freeWifiLock();
71  		} else if (state == PlayerStateEnum.Playing) {
72  			lockWifiIfNecessary(null, false);
73  		}
74  	}
75  
76  	public void playlistChanged(Interval target) {
77  		// do nothing
78  	}
79  
80  	public void randomChanged(Random random) {
81  		// do nothing
82  	}
83  
84  	public void repeatChanged(Repeat repeat) {
85  		// do nothing
86  	}
87  
88  	public void trackChanged(PlaylistItem track, boolean play,
89  			int positionMillis) {
90  		if (play) {
91  			if (track.getTrack().isLocal()) {
92  				freeWifiLock();
93  				return;
94  			}
95  			lockWifiIfNecessary(track.getTrack(), false);
96  		}
97  	}
98  
99  	private void lockWifiIfNecessary(final TrackMetadataBean track,
100 			final boolean wifiConnected) {
101 		if (wifiLock != null) {
102 			// already locked. bail out
103 			return;
104 		}
105 		if (track == null) {
106 			final TrackMetadataBean t = app.getPlaylist()
107 					.getCurrentlyPlayingTrack();
108 			if (t != null) {
109 				lockWifiIfNecessary(t, wifiConnected);
110 			}
111 			return;
112 		}
113 		if (track.isLocal()) {
114 			return;
115 		}
116 		if (!wifiConnected && !isWifiConnected()) {
117 			return;
118 		}
119 		// okay, might be using WiFi to access the server. Lock it
120 		final WifiManager wifiMan = (WifiManager) app
121 				.getSystemService(Context.WIFI_SERVICE);
122 		wifiLock = wifiMan.createWifiLock(NetworkHandler.class.getName());
123 		Log.i(NetworkHandler.class.getSimpleName(), "Acquiring WiFi lock");
124 		wifiLock.acquire();
125 	}
126 
127 	private boolean isWifiConnected() {
128 		final ConnectivityManager manager = (ConnectivityManager) app
129 				.getSystemService(Context.CONNECTIVITY_SERVICE);
130 		final NetworkInfo info = manager
131 				.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
132 		return info != null && info.isConnected();
133 	}
134 
135 	private WifiLock wifiLock = null;
136 
137 	public void trackPositionChanged(int position, boolean playing) {
138 		// do nothing
139 	}
140 
141 	/***
142 	 * Destroys this handler and drops any power locks being held.
143 	 */
144 	void destroy() {
145 		app.unregisterReceiver(this);
146 		freeWifiLock();
147 	}
148 
149 	private void freeWifiLock() {
150 		if (wifiLock != null) {
151 			Log.i(NetworkHandler.class.getSimpleName(), "Dropping WiFi lock");
152 			wifiLock.release();
153 			wifiLock = null;
154 		}
155 	}
156 
157 	@Override
158 	public void onReceive(Context context, Intent intent) {
159 		if (intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY,
160 				false)) {
161 			freeWifiLock();
162 			onNetworkStateChanged(true);
163 			return;
164 		}
165 		if (intent
166 				.getBooleanExtra(ConnectivityManager.EXTRA_IS_FAILOVER, false)) {
167 			onNetworkStateChanged(!app.getStateHandler().getStartupState().online);
168 			final boolean isPlaying = app.getPlaylist().getPlaybackState() == PlayerStateEnum.Playing;
169 			if (!isPlaying) {
170 				// do nothing
171 				return;
172 			}
173 			final NetworkInfo newInfo = intent
174 					.getParcelableExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO);
175 			if (newInfo == null) {
176 				// probably still reconnecting. Do nothing and wait
177 				return;
178 			}
179 			if (newInfo.getType() == ConnectivityManager.TYPE_WIFI) {
180 				lockWifiIfNecessary(null, true);
181 				return;
182 			}
183 			// switching to a non-WIFI Internet provider. The playback might
184 			// still be using WIFI access though: check this possibility
185 			if (!isWifiConnected()) {
186 				freeWifiLock();
187 			}
188 		}
189 	}
190 
191 	private boolean getNetworkOffline() {
192 		final ConnectivityManager manager = (ConnectivityManager) app
193 				.getSystemService(Context.CONNECTIVITY_SERVICE);
194 		final NetworkInfo[] infos = manager.getAllNetworkInfo();
195 		if (infos == null) {
196 			return true;
197 		}
198 		for (final NetworkInfo info : infos) {
199 			if (info == null) {
200 				continue;
201 			}
202 			if (info.isConnected()) {
203 				return false;
204 			}
205 		}
206 		return true;
207 	}
208 
209 	/***
210 	 * Computes the off-line status of the phone.
211 	 * 
212 	 * @return computed off-line state.
213 	 */
214 	private boolean computeOffline() {
215 		if (!app.getStateHandler().getStartupState().online) {
216 			return true;
217 		}
218 		return getNetworkOffline();
219 	}
220 
221 	private boolean lastOfflineValue = true;
222 
223 	/***
224 	 * Sets the offline status. Note that this method may be used to force the
225 	 * offline state only - it cannot be used to force us being online.
226 	 * 
227 	 * @param offline
228 	 *            <code>true</code> forces the offline status,
229 	 *            <code>false</code> resumes the network status auto-detection.
230 	 */
231 	public void setForcedOffline(boolean offline) {
232 		app.getStateHandler().getStartupState().online = !offline;
233 		onNetworkStateChanged(computeOffline());
234 	}
235 
236 	private void onNetworkStateChanged(final boolean networkOffline) {
237 		if (networkOffline == lastOfflineValue) {
238 			return;
239 		}
240 		lastOfflineValue = networkOffline;
241 		app.getBackgroundTasks().setOffline(networkOffline);
242 		app.getBus().getInvocator(IApplicationListener.class, true).offline(
243 				networkOffline);
244 	}
245 
246 	/***
247 	 * Checks if the player is currently off-line.
248 	 * 
249 	 * @return <code>true</code> if the player is off-line, <code>false</code>
250 	 *         if it is on-line.
251 	 */
252 	public boolean isOffline() {
253 		return lastOfflineValue;
254 	}
255 }