michael@0: /* michael@0: * Copyright (C) 2013 Square, Inc. michael@0: * michael@0: * Licensed under the Apache License, Version 2.0 (the "License"); michael@0: * you may not use this file except in compliance with the License. michael@0: * You may obtain a copy of the License at michael@0: * michael@0: * http://www.apache.org/licenses/LICENSE-2.0 michael@0: * michael@0: * Unless required by applicable law or agreed to in writing, software michael@0: * distributed under the License is distributed on an "AS IS" BASIS, michael@0: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. michael@0: * See the License for the specific language governing permissions and michael@0: * limitations under the License. michael@0: */ michael@0: package com.squareup.picasso; michael@0: michael@0: import android.Manifest; michael@0: import android.content.BroadcastReceiver; michael@0: import android.content.Context; michael@0: import android.content.Intent; michael@0: import android.content.IntentFilter; michael@0: import android.net.ConnectivityManager; michael@0: import android.net.NetworkInfo; michael@0: import android.os.Bundle; michael@0: import android.os.Handler; michael@0: import android.os.HandlerThread; michael@0: import android.os.Looper; michael@0: import android.os.Message; michael@0: import java.util.ArrayList; michael@0: import java.util.LinkedHashMap; michael@0: import java.util.List; michael@0: import java.util.Map; michael@0: import java.util.concurrent.ExecutorService; michael@0: michael@0: import static android.content.Context.CONNECTIVITY_SERVICE; michael@0: import static android.content.Intent.ACTION_AIRPLANE_MODE_CHANGED; michael@0: import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; michael@0: import static android.os.Process.THREAD_PRIORITY_BACKGROUND; michael@0: import static com.squareup.picasso.BitmapHunter.forRequest; michael@0: michael@0: class Dispatcher { michael@0: private static final int RETRY_DELAY = 500; michael@0: private static final int AIRPLANE_MODE_ON = 1; michael@0: private static final int AIRPLANE_MODE_OFF = 0; michael@0: michael@0: static final int REQUEST_SUBMIT = 1; michael@0: static final int REQUEST_CANCEL = 2; michael@0: static final int REQUEST_GCED = 3; michael@0: static final int HUNTER_COMPLETE = 4; michael@0: static final int HUNTER_RETRY = 5; michael@0: static final int HUNTER_DECODE_FAILED = 6; michael@0: static final int HUNTER_DELAY_NEXT_BATCH = 7; michael@0: static final int HUNTER_BATCH_COMPLETE = 8; michael@0: static final int NETWORK_STATE_CHANGE = 9; michael@0: static final int AIRPLANE_MODE_CHANGE = 10; michael@0: michael@0: private static final String DISPATCHER_THREAD_NAME = "Dispatcher"; michael@0: private static final int BATCH_DELAY = 200; // ms michael@0: michael@0: final DispatcherThread dispatcherThread; michael@0: final Context context; michael@0: final ExecutorService service; michael@0: final Downloader downloader; michael@0: final Map hunterMap; michael@0: final Handler handler; michael@0: final Handler mainThreadHandler; michael@0: final Cache cache; michael@0: final Stats stats; michael@0: final List batch; michael@0: final NetworkBroadcastReceiver receiver; michael@0: michael@0: NetworkInfo networkInfo; michael@0: boolean airplaneMode; michael@0: michael@0: Dispatcher(Context context, ExecutorService service, Handler mainThreadHandler, michael@0: Downloader downloader, Cache cache, Stats stats) { michael@0: this.dispatcherThread = new DispatcherThread(); michael@0: this.dispatcherThread.start(); michael@0: this.context = context; michael@0: this.service = service; michael@0: this.hunterMap = new LinkedHashMap(); michael@0: this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this); michael@0: this.downloader = downloader; michael@0: this.mainThreadHandler = mainThreadHandler; michael@0: this.cache = cache; michael@0: this.stats = stats; michael@0: this.batch = new ArrayList(4); michael@0: this.airplaneMode = Utils.isAirplaneModeOn(this.context); michael@0: this.receiver = new NetworkBroadcastReceiver(this.context); michael@0: receiver.register(); michael@0: } michael@0: michael@0: void shutdown() { michael@0: service.shutdown(); michael@0: dispatcherThread.quit(); michael@0: receiver.unregister(); michael@0: } michael@0: michael@0: void dispatchSubmit(Action action) { michael@0: handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action)); michael@0: } michael@0: michael@0: void dispatchCancel(Action action) { michael@0: handler.sendMessage(handler.obtainMessage(REQUEST_CANCEL, action)); michael@0: } michael@0: michael@0: void dispatchComplete(BitmapHunter hunter) { michael@0: handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter)); michael@0: } michael@0: michael@0: void dispatchRetry(BitmapHunter hunter) { michael@0: handler.sendMessageDelayed(handler.obtainMessage(HUNTER_RETRY, hunter), RETRY_DELAY); michael@0: } michael@0: michael@0: void dispatchFailed(BitmapHunter hunter) { michael@0: handler.sendMessage(handler.obtainMessage(HUNTER_DECODE_FAILED, hunter)); michael@0: } michael@0: michael@0: void dispatchNetworkStateChange(NetworkInfo info) { michael@0: handler.sendMessage(handler.obtainMessage(NETWORK_STATE_CHANGE, info)); michael@0: } michael@0: michael@0: void dispatchAirplaneModeChange(boolean airplaneMode) { michael@0: handler.sendMessage(handler.obtainMessage(AIRPLANE_MODE_CHANGE, michael@0: airplaneMode ? AIRPLANE_MODE_ON : AIRPLANE_MODE_OFF, 0)); michael@0: } michael@0: michael@0: void performSubmit(Action action) { michael@0: BitmapHunter hunter = hunterMap.get(action.getKey()); michael@0: if (hunter != null) { michael@0: hunter.attach(action); michael@0: return; michael@0: } michael@0: michael@0: if (service.isShutdown()) { michael@0: return; michael@0: } michael@0: michael@0: hunter = forRequest(context, action.getPicasso(), this, cache, stats, action, downloader); michael@0: hunter.future = service.submit(hunter); michael@0: hunterMap.put(action.getKey(), hunter); michael@0: } michael@0: michael@0: void performCancel(Action action) { michael@0: String key = action.getKey(); michael@0: BitmapHunter hunter = hunterMap.get(key); michael@0: if (hunter != null) { michael@0: hunter.detach(action); michael@0: if (hunter.cancel()) { michael@0: hunterMap.remove(key); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void performRetry(BitmapHunter hunter) { michael@0: if (hunter.isCancelled()) return; michael@0: michael@0: if (service.isShutdown()) { michael@0: performError(hunter); michael@0: return; michael@0: } michael@0: michael@0: if (hunter.shouldRetry(airplaneMode, networkInfo)) { michael@0: hunter.future = service.submit(hunter); michael@0: } else { michael@0: performError(hunter); michael@0: } michael@0: } michael@0: michael@0: void performComplete(BitmapHunter hunter) { michael@0: if (!hunter.shouldSkipMemoryCache()) { michael@0: cache.set(hunter.getKey(), hunter.getResult()); michael@0: } michael@0: hunterMap.remove(hunter.getKey()); michael@0: batch(hunter); michael@0: } michael@0: michael@0: void performBatchComplete() { michael@0: List copy = new ArrayList(batch); michael@0: batch.clear(); michael@0: mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy)); michael@0: } michael@0: michael@0: void performError(BitmapHunter hunter) { michael@0: hunterMap.remove(hunter.getKey()); michael@0: batch(hunter); michael@0: } michael@0: michael@0: void performAirplaneModeChange(boolean airplaneMode) { michael@0: this.airplaneMode = airplaneMode; michael@0: } michael@0: michael@0: void performNetworkStateChange(NetworkInfo info) { michael@0: networkInfo = info; michael@0: if (service instanceof PicassoExecutorService) { michael@0: ((PicassoExecutorService) service).adjustThreadCount(info); michael@0: } michael@0: } michael@0: michael@0: private void batch(BitmapHunter hunter) { michael@0: if (hunter.isCancelled()) { michael@0: return; michael@0: } michael@0: batch.add(hunter); michael@0: if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) { michael@0: handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY); michael@0: } michael@0: } michael@0: michael@0: private static class DispatcherHandler extends Handler { michael@0: private final Dispatcher dispatcher; michael@0: michael@0: public DispatcherHandler(Looper looper, Dispatcher dispatcher) { michael@0: super(looper); michael@0: this.dispatcher = dispatcher; michael@0: } michael@0: michael@0: @Override public void handleMessage(final Message msg) { michael@0: switch (msg.what) { michael@0: case REQUEST_SUBMIT: { michael@0: Action action = (Action) msg.obj; michael@0: dispatcher.performSubmit(action); michael@0: break; michael@0: } michael@0: case REQUEST_CANCEL: { michael@0: Action action = (Action) msg.obj; michael@0: dispatcher.performCancel(action); michael@0: break; michael@0: } michael@0: case HUNTER_COMPLETE: { michael@0: BitmapHunter hunter = (BitmapHunter) msg.obj; michael@0: dispatcher.performComplete(hunter); michael@0: break; michael@0: } michael@0: case HUNTER_RETRY: { michael@0: BitmapHunter hunter = (BitmapHunter) msg.obj; michael@0: dispatcher.performRetry(hunter); michael@0: break; michael@0: } michael@0: case HUNTER_DECODE_FAILED: { michael@0: BitmapHunter hunter = (BitmapHunter) msg.obj; michael@0: dispatcher.performError(hunter); michael@0: break; michael@0: } michael@0: case HUNTER_DELAY_NEXT_BATCH: { michael@0: dispatcher.performBatchComplete(); michael@0: break; michael@0: } michael@0: case NETWORK_STATE_CHANGE: { michael@0: NetworkInfo info = (NetworkInfo) msg.obj; michael@0: dispatcher.performNetworkStateChange(info); michael@0: break; michael@0: } michael@0: case AIRPLANE_MODE_CHANGE: { michael@0: dispatcher.performAirplaneModeChange(msg.arg1 == AIRPLANE_MODE_ON); michael@0: break; michael@0: } michael@0: default: michael@0: Picasso.HANDLER.post(new Runnable() { michael@0: @Override public void run() { michael@0: throw new AssertionError("Unknown handler message received: " + msg.what); michael@0: } michael@0: }); michael@0: } michael@0: } michael@0: } michael@0: michael@0: static class DispatcherThread extends HandlerThread { michael@0: DispatcherThread() { michael@0: super(Utils.THREAD_PREFIX + DISPATCHER_THREAD_NAME, THREAD_PRIORITY_BACKGROUND); michael@0: } michael@0: } michael@0: michael@0: private class NetworkBroadcastReceiver extends BroadcastReceiver { michael@0: private static final String EXTRA_AIRPLANE_STATE = "state"; michael@0: michael@0: private final ConnectivityManager connectivityManager; michael@0: michael@0: NetworkBroadcastReceiver(Context context) { michael@0: connectivityManager = (ConnectivityManager) context.getSystemService(CONNECTIVITY_SERVICE); michael@0: } michael@0: michael@0: void register() { michael@0: boolean shouldScanState = service instanceof PicassoExecutorService && // michael@0: Utils.hasPermission(context, Manifest.permission.ACCESS_NETWORK_STATE); michael@0: IntentFilter filter = new IntentFilter(); michael@0: filter.addAction(ACTION_AIRPLANE_MODE_CHANGED); michael@0: if (shouldScanState) { michael@0: filter.addAction(CONNECTIVITY_ACTION); michael@0: } michael@0: context.registerReceiver(this, filter); michael@0: } michael@0: michael@0: void unregister() { michael@0: context.unregisterReceiver(this); michael@0: } michael@0: michael@0: @Override public void onReceive(Context context, Intent intent) { michael@0: // On some versions of Android this may be called with a null Intent michael@0: if (null == intent) { michael@0: return; michael@0: } michael@0: michael@0: String action = intent.getAction(); michael@0: Bundle extras = intent.getExtras(); michael@0: michael@0: if (ACTION_AIRPLANE_MODE_CHANGED.equals(action)) { michael@0: dispatchAirplaneModeChange(extras.getBoolean(EXTRA_AIRPLANE_STATE, false)); michael@0: } else if (CONNECTIVITY_ACTION.equals(action)) { michael@0: dispatchNetworkStateChange(connectivityManager.getActiveNetworkInfo()); michael@0: } michael@0: } michael@0: } michael@0: }