/* * Mirror - Yet another Sketch Mirror App for Android. * Copyright (C) 2016 Zhihu Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.zhihu.android.app.mirror.app; import android.app.Service; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.nsd.NsdManager; import android.net.nsd.NsdServiceInfo; import android.os.IBinder; import android.text.TextUtils; import com.google.gson.Gson; import java.io.IOException; import java.util.concurrent.TimeUnit; import com.zhihu.android.app.mirror.event.MirrorFoundEvent; import com.zhihu.android.app.mirror.event.MirrorLostEvent; import com.zhihu.android.app.mirror.event.MirrorMessageEvent; import com.zhihu.android.app.mirror.event.MirrorResolveFailedEvent; import com.zhihu.android.app.mirror.event.MirrorResolveSuccessEvent; import com.zhihu.android.app.mirror.event.MirrorSelectedEvent; import com.zhihu.android.app.mirror.event.ViewSwitchedEvent; import com.zhihu.android.app.mirror.event.WebSocketCloseEvent; import com.zhihu.android.app.mirror.event.WebSocketFailureEvent; import com.zhihu.android.app.mirror.event.WifiConnectedEvent; import com.zhihu.android.app.mirror.event.WifiConnectingEvent; import com.zhihu.android.app.mirror.event.WifiDisconnectedEvent; import com.zhihu.android.app.mirror.model.Message; import com.zhihu.android.app.mirror.model.MirrorInfo; import com.zhihu.android.app.mirror.util.NetworkUtils; import com.zhihu.android.app.mirror.util.MirrorUtils; import com.zhihu.android.app.mirror.util.PreferenceUtils; import com.zhihu.android.app.mirror.util.RxBus; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; import okhttp3.ResponseBody; import okhttp3.ws.WebSocket; import okhttp3.ws.WebSocketCall; import okhttp3.ws.WebSocketListener; import okio.Buffer; import rx.Subscriber; import rx.Subscription; import rx.android.schedulers.AndroidSchedulers; import rx.subscriptions.CompositeSubscription; public class MirrorService extends Service implements WebSocketListener { private static final String SERVICE_TYPE = MirrorUtils.MIRROR_TYPE; private static final int PROTOCOL_TYPE = NsdManager.PROTOCOL_DNS_SD; private BroadcastReceiver mWifiReceiver; private boolean mIsWifiConnected; private boolean mIsWifiDisconnected; private NsdManager mNsdManager; private NsdManager.DiscoveryListener mDiscoveryListener; private WebSocketCall mWebSocketCall; private Gson mGson; private CompositeSubscription mCompositeSubscription; @Override public IBinder onBind(Intent intent) { return null; } @Override public int onStartCommand(Intent intent, int flags, int startId) { setupWifiConnect(); setupRxBus(); return START_STICKY; } @Override public void onDestroy() { unregisterReceiverSafety(); closeDiscoveryListenerSafety(); closeWebSocketSafety(); if (mCompositeSubscription != null) { mCompositeSubscription.unsubscribe(); } } private void unregisterReceiverSafety() { try { if (mWifiReceiver != null) { unregisterReceiver(mWifiReceiver); } } catch (Exception e) { e.printStackTrace(); } } private void closeDiscoveryListenerSafety() { try { if (mNsdManager != null && mDiscoveryListener != null) { mNsdManager.stopServiceDiscovery(mDiscoveryListener); } } catch (Exception e) { e.printStackTrace(); } } private void closeWebSocketSafety() { try { if (mWebSocketCall != null) { mWebSocketCall.cancel(); } } catch (Exception e) { e.printStackTrace(); } } // ============================================================================================= private void setupWifiConnect() { unregisterReceiverSafety(); closeDiscoveryListenerSafety(); closeWebSocketSafety(); mWifiReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (!TextUtils.equals(ConnectivityManager.CONNECTIVITY_ACTION, intent.getAction())) { return; } ConnectivityManager manager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); NetworkInfo info = manager.getActiveNetworkInfo(); if (info == null) { onWifiDisconnected(); return; } if (info.getType() != ConnectivityManager.TYPE_WIFI) { onWifiDisconnected(); return; } if (!info.isConnected()) { onWifiConnecting(); } else { onWifiConnected(); } } }; IntentFilter filter = new IntentFilter(); filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); registerReceiver(mWifiReceiver, filter); if (!NetworkUtils.isWifiConnected(this)) { NetworkUtils.setWifiEnabled(this, true); } } private void onWifiConnecting() { RxBus.getInstance().post(new WifiConnectingEvent()); } private void onWifiConnected() { if (mIsWifiConnected) { return; } mIsWifiConnected = true; mIsWifiDisconnected = false; closeDiscoveryListenerSafety(); RxBus.getInstance().post(new WifiConnectedEvent()); mNsdManager = (NsdManager) getSystemService(NSD_SERVICE); mDiscoveryListener = buildDiscoveryListener(); mNsdManager.discoverServices(SERVICE_TYPE, PROTOCOL_TYPE, mDiscoveryListener); } private NsdManager.DiscoveryListener buildDiscoveryListener() { return new NsdManager.DiscoveryListener() { @Override public void onStartDiscoveryFailed(String serviceType, int errorCode) { // DO NOTHING } @Override public void onStopDiscoveryFailed(String serviceType, int errorCode) { // DO NOTHING } @Override public void onDiscoveryStarted(String serviceType) { // DO NOTHING } @Override public void onDiscoveryStopped(String serviceType) { // DO NOTHING } @Override public void onServiceFound(NsdServiceInfo nsdServiceInfo) { if (nsdServiceInfo.getServiceName().startsWith(MirrorUtils.MIRROR_FILTER)) { RxBus.getInstance().post(new MirrorFoundEvent(new MirrorInfo(nsdServiceInfo))); } } @Override public void onServiceLost(NsdServiceInfo nsdServiceInfo) { if (nsdServiceInfo.getServiceName().startsWith(MirrorUtils.MIRROR_FILTER)) { RxBus.getInstance().post(new MirrorLostEvent(new MirrorInfo(nsdServiceInfo))); } } }; } private void onWifiDisconnected() { if (mIsWifiDisconnected) { return; } mIsWifiConnected = false; mIsWifiDisconnected = true; closeDiscoveryListenerSafety(); closeWebSocketSafety(); RxBus.getInstance().post(new WifiDisconnectedEvent()); } // ============================================================================================= private void setupRxBus() { if (mCompositeSubscription != null) { mCompositeSubscription.unsubscribe(); } mCompositeSubscription = new CompositeSubscription(); Subscription subscription = RxBus.getInstance().toObservable(MirrorSelectedEvent.class) .onBackpressureBuffer() .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<MirrorSelectedEvent>() { @Override public void onCompleted() { // DO NOTHING } @Override public void onError(Throwable e) { e.printStackTrace(); } @Override public void onNext(MirrorSelectedEvent event) { onMirrorSelectedEvent(event); } }); mCompositeSubscription.add(subscription); subscription = RxBus.getInstance().toObservable(ViewSwitchedEvent.class) .onBackpressureBuffer() .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<ViewSwitchedEvent>() { @Override public void onCompleted() { // DO NOTHING } @Override public void onError(Throwable e) { e.printStackTrace(); } @Override public void onNext(ViewSwitchedEvent event) { onViewSwitchedEvent(event); } }); mCompositeSubscription.add(subscription); } private void onMirrorSelectedEvent(MirrorSelectedEvent event) { closeWebSocketSafety(); mNsdManager.resolveService(event.getMirrorInfo().getNsdServiceInfo(), buildResolveListener()); } private NsdManager.ResolveListener buildResolveListener() { return new NsdManager.ResolveListener() { @Override public void onResolveFailed(NsdServiceInfo nsdServiceInfo, int errorCode) { MirrorService.this.onResolveFailed(nsdServiceInfo); } @Override public void onServiceResolved(NsdServiceInfo nsdServiceInfo) { onResolveSuccess(nsdServiceInfo); } }; } private void onResolveFailed(NsdServiceInfo nsdServiceInfo) { RxBus.getInstance().post(new MirrorResolveFailedEvent(new MirrorInfo(nsdServiceInfo))); } private void onResolveSuccess(NsdServiceInfo nsdServiceInfo) { MirrorInfo mirrorInfo = new MirrorInfo(nsdServiceInfo); PreferenceUtils.setMirror(this, MirrorUtils.buildMirrorHttpUrl(mirrorInfo)); RxBus.getInstance().post(new MirrorResolveSuccessEvent(mirrorInfo)); OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(MirrorUtils.MIRROR_CONNECT_TIMEOUT, TimeUnit.MILLISECONDS) .readTimeout(MirrorUtils.MIRROR_READ_TIMEOUT, TimeUnit.MILLISECONDS) .retryOnConnectionFailure(true) .build(); Request request = new Request.Builder() .url(MirrorUtils.buildMirrorWebSocketUrl(mirrorInfo)) .build(); mWebSocketCall = WebSocketCall.create(client, request); mWebSocketCall.enqueue(this); } private void onViewSwitchedEvent(ViewSwitchedEvent event) { if (event.getCurrentType() == ViewSwitchedEvent.FLAG_MIRROR_LIST) { closeWebSocketSafety(); } } // ============================================================================================= @Override public void onOpen(WebSocket webSocket, Response response) { mGson = new Gson(); String json = mGson.toJson(MirrorUtils.buildHandshake(this)); try { webSocket.sendMessage(RequestBody.create(WebSocket.TEXT, json)); } catch (IOException e) { onFailure(e, null); } } @Override public void onFailure(IOException e, Response response) { e.printStackTrace(); // sometimes EOF, how to fix? RxBus.getInstance().post(new WebSocketFailureEvent(e, response)); } @Override public void onMessage(ResponseBody body) throws IOException { Message message = mGson.fromJson(body.string(), Message.class); // not TYPE_DISCONNECTED if (TextUtils.equals(MirrorUtils.TYPE_CONNECTED, message.getType())) { PreferenceUtils.setToken(this, message.getContent().getToken()); PreferenceUtils.setDevice(this, message.getContent().getDevice()); } RxBus.getInstance().post(new MirrorMessageEvent(message)); body.close(); } @Override public void onPong(Buffer payload) { // DO NOTHING } @Override public void onClose(int code, String reason) { RxBus.getInstance().post(new WebSocketCloseEvent(code, reason)); } }