com.samsung.soundscape.util.ConnectivityManager.java Source code

Java tutorial

Introduction

Here is the source code for com.samsung.soundscape.util.ConnectivityManager.java

Source

/*******************************************************************************
 * Copyright (c) 2015 Samsung Electronics
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *******************************************************************************/

package com.samsung.soundscape.util;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.net.wifi.WifiManager;

import com.samsung.multiscreen.Channel;
import com.samsung.multiscreen.Client;
import com.samsung.multiscreen.Message;
import com.samsung.multiscreen.Result;
import com.samsung.multiscreen.Search;
import com.samsung.multiscreen.Service;
import com.samsung.multiscreen.util.JSONUtil;
import com.samsung.multiscreen.util.RunUtil;
import com.samsung.soundscape.App;
import com.samsung.soundscape.R;
import com.samsung.soundscape.adapter.ServiceAdapter;
import com.samsung.soundscape.events.AddTrackEvent;
import com.samsung.soundscape.events.AppStateEvent;
import com.samsung.soundscape.events.AssignColorEvent;
import com.samsung.soundscape.events.ConnectionChangedEvent;
import com.samsung.soundscape.events.RemoveTrackEvent;
import com.samsung.soundscape.events.ServiceChangedEvent;
import com.samsung.soundscape.events.TrackPlaybackEvent;
import com.samsung.soundscape.events.TrackStatusEvent;
import com.samsung.soundscape.model.AppState;
import com.samsung.soundscape.model.CurrentStatus;
import com.samsung.soundscape.model.Track;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.HashMap;

import de.greenrobot.event.EventBus;

/**
 * Provides the Samsung MultiScreen functions.
 */
public class ConnectivityManager {
    public enum ServiceType {
        //Other unknown type
        Other,
        //Samsung smart TV.
        TV,
        //Samsung smart speaker.
        Speaker
    }

    public static final String EVENT_ADD_TRACK = "addTrack";
    public static final String EVENT_REMOVE_TRACK = "removeTrack";
    public static final String EVENT_TRACK_STATUS = "trackStatus";
    public static final String EVENT_APP_STATE_REQUEST = "appStateRequest";
    public static final String EVENT_APP_STATE = "appState";
    public static final String EVENT_TRACK_START = "trackStart";
    public static final String EVENT_TRACK_END = "trackEnd";
    public static final String EVENT_PLAY = "play";
    public static final String EVENT_PAUSE = "pause";
    public static final String EVENT_NEXT = "next";
    public static final String EVENT_SEEK = "seek";
    public static final String EVENT_ASSIGN_COLOR = "assignColor";
    public static final String EVENT_ASSIGN_COLOR_REQUEST = "assignColorRequest";
    public static final String EVENT_VOL_DOWN = "volDown";
    public static final String EVENT_VOL_UP = "volUp";

    /**
     * An singleton instance of this class
     */
    private static ConnectivityManager instance = null;

    /**
     * A lock used to synchronize creation of this object and access to the service map.
     */
    protected static final Object lock = new Object();

    /**
     * The Search object which is going to run discovery service.
     */
    private Search search = null;

    /**
     * Multiscreen TV service
     */
    private com.samsung.multiscreen.Service service;

    /**
     * Multiscreen TV application
     */
    private com.samsung.multiscreen.Application mMultiscreenApp;

    /**
     * The adapter used to maintain TV service list.
     */
    private ServiceAdapter adapter;

    /**
     * The flag shows that application is going to exit.
     */
    private boolean isExisting = false;

    /**
     * Returns the instance.
     *
     * @return
     */
    public static ConnectivityManager getInstance() {
        if (instance == null) {
            synchronized (lock) {
                if (instance == null) {
                    instance = new ConnectivityManager();
                }
            }
        }
        return instance;
    }

    private ConnectivityManager() {
        //Register Wifi state listener.
        registerWiFiStateListener();
    }

    /**
     * Adapter for the service list dialog.
     */
    public ServiceAdapter getServiceAdapter() {
        return adapter;
    }

    public void setServiceAdapter(ServiceAdapter adapter) {
        this.adapter = adapter;
    }

    /**
     * Check if discovery process is running.
     *
     * @return true discovery is running otherwise false.
     */
    public boolean isDiscovering() {
        return (search != null && search.isSearching());
    }

    /**
     * start TV discovery.
     */
    public void startDiscovery() {
        Util.d("startDiscovery");

        //Create the search object if it is null.
        if (search == null) {
            search = Service.search(App.getInstance());
            search.setOnServiceFoundListener(mOnServiceFoundListener);
            search.setOnServiceLostListener(mOnServiceLostListener);
        }

        //When WiFi is connected and search process is not running.
        if (Util.isWiFiConnected() && !search.isSearching()) {

            if (!search.isStarting()) {
                //Clear the TV list.
                if (adapter != null) {
                    adapter.clear();

                    //Notify UI to update cast icon.
                    EventBus.getDefault().post(new ServiceChangedEvent());
                }

                //Start discovery.
                search.start();
            }
        }
    }

    private Search.OnServiceFoundListener mOnServiceFoundListener = new Search.OnServiceFoundListener() {
        @Override
        public void onFound(Service service) {
            Util.d("Service onFound: " + service);

            //TV is found, update the TV list.
            updateTVList(service);
        }
    };
    private Search.OnServiceLostListener mOnServiceLostListener = new Search.OnServiceLostListener() {
        @Override
        public void onLost(Service service) {
            //TV is removed. We will remove the TV from TV list.
            removeTV(service);
        }
    };

    /**
     * Stop TV discovery.
     */
    public void stopDiscovery() {
        Util.d("stopDiscovery");

        //Stop discovery if search object it not null and it is search.
        if (search != null && search.isSearching()) {
            if (!search.isStopping()) {
                try {
                    search.stop();
                } catch (Exception e) { //ignore any error during stop search.
                }
            }
        }
    }

    /**
     * Remove TV from the TV list.
     *
     * @param service
     */
    private void removeTV(Service service) {
        if (service == null) {
            return;
        }

        //Remove the service if it exists.
        if (adapter.contains(service)) {
            adapter.remove(service);

            //Notify service is changed.
            notifyDataChange();
        }
    }

    /**
     * Add TV into TV list.
     *
     * @param service the TV service to be added.
     */
    private void updateTVList(Service service) {
        if (service == null || adapter == null) {
            return;
        }

        //Add if does not exist or replace it.
        if (!adapter.contains(service)) {
            //Do not add it to the list when the service is connected already.
            if (!service.equals(this.service) || !isTVConnected()) {
                adapter.add(service);
            }
        } else {
            //Replace the service with new service.
            adapter.replace(service);
        }

        //Notify listeners that service has changed. Please update UI accordingly.
        notifyDataChange();
    }

    public void addConnectedServerToList() {
        if (service == null) {
            return;
        }

        //Add if does not exist or replace it.
        if (!getServiceAdapter().contains(service)) {
            adapter.add(service);
        } else {
            //Replace the service with new service.
            adapter.replace(service);
        }

        //Notify listeners that service has changed. Please update UI accordingly.
        notifyDataChange();

    }

    public void removeConnectedServiceFromList() {
        if (service != null && adapter != null) {
            adapter.remove(service);
            adapter.notifyDataSetChanged();
        }
    }

    /**
     * Notify TV list adapter that data set is changed.
     */
    void notifyDataChange() {
        //Run it at UI thread.
        RunUtil.runOnUI(new Runnable() {
            @Override
            public void run() {

                //notify data set changes when adapter is not null.
                if (adapter != null) {
                    adapter.notifyDataSetChanged();
                }
            }
        });

        //Notify UI to update cast icon.
        EventBus.getDefault().post(new ServiceChangedEvent());
    }

    /**
     * Set the TV service.
     *
     * @param service the new TV service to be used.
     */
    public void setService(final Service service) {
        if (this.service != null) {

            //Launch the TV mMultiscreenApp directly if we already got the TV service.
            if (this.service.equals(service)) {

                //Launch the TV app it is not connected.
                //Do nothing if it is already connected.
                if (!mMultiscreenApp.isConnected()) {
                    launchApplication();
                }

            } else {

                //If different TV is selected, disconnect the previous application.
                if (mMultiscreenApp != null && mMultiscreenApp.isConnected()) {
                    mMultiscreenApp.disconnect(true, new Result<Client>() {
                        @Override
                        public void onSuccess(Client client) {
                            //disconnect onSuccess, update service.
                            updateServiceAndConnect(service);
                        }

                        @Override
                        public void onError(com.samsung.multiscreen.Error error) {
                            //disconnect failed.
                            Util.e("disconnect onError: " + error.getMessage());

                            //Update service.
                            updateServiceAndConnect(service);
                        }
                    });
                } else {
                    updateServiceAndConnect(service);
                }
            }
        } else {
            //connect to a new TV.
            updateServiceAndConnect(service);
        }
    }

    public Service getService() {
        return service;
    }

    /**
     * Update the current service and start to launch TV application.
     *
     * @param service the new TV service.
     */
    private void updateServiceAndConnect(Service service) {
        this.service = service;

        //Start to launch TV application.
        if (this.service != null) {
            launchApplication();
        }
    }

    /**
     * Clean up the service.
     */
    public void clearService() {
        //Shows that application is existing.
        isExisting = true;

        //Unregister the WiFi state listener.
        try {
            App.getInstance().unregisterReceiver(mWifiStateChangedReceiver);
        } catch (Exception e) {//ignore if there is error.
        }

        //Disconnect TV if it is connected.
        if (isTVConnected()) {
            mMultiscreenApp.disconnect();
        }

        service = null;
    }

    /**
     * Check if TV is connected already.
     *
     * @return true if TV is connected otherwise false.
     */
    public boolean isTVConnected() {
        return service != null && mMultiscreenApp != null && mMultiscreenApp.isConnected();
    }

    /**
     * Return the service type of the connected service.
     * @return
     */
    public ServiceType getConnectedServiceType() {
        return getServiceType(this.service);
    }

    /**
     * Return the service type of given service.
     * @param service
     * @return
     */
    public ServiceType getServiceType(Service service) {
        if (service == null) {
            return ServiceType.Other;
        }

        String type = service.getType();
        if (type.toLowerCase().endsWith("speaker")) {
            return ServiceType.Speaker;
        }

        return ServiceType.TV;
    }

    /**
     * Makes connection to the TV and start the application on the TV
     * if the current service is available.
     */
    public void launchApplication() {
        if (service == null) {
            return;
        }

        //Parse Application Url.
        Uri url = Uri.parse(App.getInstance().getString(R.string.app_url));

        // Get an instance of Application.
        mMultiscreenApp = service.createApplication(url, App.getInstance().getString(R.string.channel_id));

        //Set the connection timeout to 20 seconds.
        //When the TV is unavailable after 20 seconds, onDisconnect event is called.
        mMultiscreenApp.setConnectionTimeout(20000);

        //Listen for the disconnect event.
        mMultiscreenApp.setOnDisconnectListener(new Channel.OnDisconnectListener() {
            @Override
            public void onDisconnect(Client client) {
                if (client != null) {

                    //Notify service change listeners.
                    EventBus.getDefault().post(new ConnectionChangedEvent(null));

                    //restart to discovery if service is disconnected and
                    // this application is not closing.
                    if (!isExisting)
                        startDiscovery();
                }
            }
        });

        // Listen for the connect event
        mMultiscreenApp.setOnConnectListener(new Channel.OnConnectListener() {
            @Override
            public void onConnect(Client client) {
                //stop discovery to save battery when a service is selected.
                stopDiscovery();

                //Notify to update UI.
                EventBus.getDefault().post(new ConnectionChangedEvent(null));
            }
        });

        // Listen for the errors.
        mMultiscreenApp.setOnErrorListener(new Channel.OnErrorListener() {
            @Override
            public void onError(com.samsung.multiscreen.Error error) {
                Util.e("setOnErrorListener: " + error.toString());
                EventBus.getDefault().post(new ConnectionChangedEvent(error.getMessage()));

                if (!isExisting)
                    startDiscovery();
            }
        });

        mMultiscreenApp.addOnMessageListener(EVENT_APP_STATE, onAppStateListener);
        mMultiscreenApp.addOnMessageListener(EVENT_TRACK_STATUS, onTrackStatusListener);
        mMultiscreenApp.addOnMessageListener(EVENT_ADD_TRACK, onAddTrackListener);
        mMultiscreenApp.addOnMessageListener(EVENT_TRACK_START, onTrackStartListener);
        mMultiscreenApp.addOnMessageListener(EVENT_TRACK_END, onTrackEndListener);
        mMultiscreenApp.addOnMessageListener(EVENT_ASSIGN_COLOR, onAssignColorListener);
        mMultiscreenApp.addOnMessageListener(EVENT_REMOVE_TRACK, onRemoveTrackColorListener);

        //Connect and launch the TV application.
        mMultiscreenApp.connect(null, 30000, new Result<Client>() {

            @Override
            public void onSuccess(Client client) {
            }

            @Override
            public void onError(com.samsung.multiscreen.Error error) {
                Util.e("connect onError: " + error.toString());

                //failed to launch TV application. Notify TV service changes.
                EventBus.getDefault().post(new ConnectionChangedEvent(error.getMessage()));
            }
        });
    }

    /**
     * Disconnect the multiscreen web application.
     */
    public void disconnect() {
        if (service != null && mMultiscreenApp != null && mMultiscreenApp.isConnected()) {
            mMultiscreenApp.removeOnMessageListeners();
            mMultiscreenApp.disconnect(true, null);
            service = null;
        }
    }

    /**
     * Set the discovery stop listener called when discovery is stopped.
     *
     * @param listener the listener.
     */
    public void setDiscoveryOnStopListener(Search.OnStopListener listener) {
        if (search != null) {
            search.setOnStopListener(listener);
        }
    }

    /**
     * Stop the discovery if it is searching and start a new discovery.
     */
    public void restartDiscovery() {
        if (search == null) {
            return;
        }

        if (search.isSearching()) {
            //Set the listener. Called when search is completely stopped.
            search.setOnStopListener(new Search.OnStopListener() {
                @Override
                public void onStop() {

                    //Clear the onStopListener.
                    search.setOnStopListener(null);

                    //Start a new discovery.
                    startDiscovery();
                }
            });

            //Start to stop discovery.
            stopDiscovery();
        } else {
            //There is no search process, start a new discovery.
            startDiscovery();
        }
    }

    /**
     * Get the clients amount.
     * @return
     */
    public int getClientCount() {
        int count = 0;

        if (isTVConnected()) {
            count = mMultiscreenApp.getClients().size();
        }

        return count;
    }

    /**
     * Send the app state reqeust.
     */
    public void requestAppState() {
        sendToTV(EVENT_APP_STATE_REQUEST, null, Message.TARGET_HOST);
    }

    /**
     * broadcast the add track event.
     * @param track
     */
    public void addTrack(Track track) {
        try {
            JSONObject obj = new JSONObject(track.toJsonString());
            sendToTV(EVENT_ADD_TRACK, obj, Message.TARGET_BROADCAST);
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    /**
     * Broadcast the remove track event.
     * @param trackId
     */
    public void removeTrack(String trackId) {
        sendToTV(EVENT_REMOVE_TRACK, trackId, Message.TARGET_BROADCAST);
    }

    /**
     * Pause the playing track.
     */
    public void pause() {
        sendToTV(EVENT_PAUSE, null, Message.TARGET_BROADCAST);
    }

    /**
     * Play the track.
     */
    public void play() {
        sendToTV(EVENT_PLAY, null, Message.TARGET_BROADCAST);
    }

    /**
     * Skip the song at the top of the playlist.
     */
    public void next() {
        sendToTV(EVENT_NEXT, null, Message.TARGET_BROADCAST);
    }

    /**
     * Seek to target position.
     */
    public void seek(int position) {
        sendToTV(EVENT_SEEK, position, Message.TARGET_BROADCAST);
    }

    /**
     * Request color assigned by server.
     */
    public void requestAssignColor() {
        sendToTV(EVENT_ASSIGN_COLOR_REQUEST, null, Message.TARGET_HOST);
    }

    /**
     * Receive the response data of app state request.
     */
    private Channel.OnMessageListener onAppStateListener = new Channel.OnMessageListener() {
        @Override
        public void onMessage(Message message) {
            if (message != null && message.getData() != null) {
                String jsonString = JSONUtil.toJSONString((HashMap) message.getData());
                EventBus.getDefault().post(new AppStateEvent(AppState.parse(jsonString)));
            }
        }
    };

    /**
     * Receive the update of track status.
     */
    private Channel.OnMessageListener onTrackStatusListener = new Channel.OnMessageListener() {
        @Override
        public void onMessage(Message message) {
            if (message != null && message.getData() != null) {

                if (message.getData() instanceof HashMap) {
                    String jsonString = JSONUtil.toJSONString((HashMap) message.getData());
                    EventBus.getDefault().post(new TrackStatusEvent(CurrentStatus.parse(jsonString)));
                }
            }
        }
    };

    /**
     * Receive the track start event.
     */
    private Channel.OnMessageListener onTrackStartListener = new Channel.OnMessageListener() {
        @Override
        public void onMessage(Message message) {
            //Util.d("onTrackStartListener: " + message.toString());

            if (message != null && message.getData() != null) {
                EventBus.getDefault()
                        .post(new TrackPlaybackEvent(message.getData().toString(), message.getEvent()));
            }
        }
    };

    /**
     * Receive the track end event.
     */
    private Channel.OnMessageListener onTrackEndListener = new Channel.OnMessageListener() {
        @Override
        public void onMessage(Message message) {
            if (message != null && message.getData() != null) {
                EventBus.getDefault()
                        .post(new TrackPlaybackEvent(message.getData().toString(), message.getEvent()));
            }
        }
    };

    /**
     * Receive the add track event.
     */
    private Channel.OnMessageListener onAddTrackListener = new Channel.OnMessageListener() {
        @Override
        public void onMessage(Message message) {
            if (message != null && message.getData() != null) {
                if (message.getData() instanceof HashMap) {
                    String jsonString = JSONUtil.toJSONString((HashMap) message.getData());
                    EventBus.getDefault().post(new AddTrackEvent(Track.parse(jsonString)));
                }
            }
        }
    };

    /**
     * Receive the add track event.
     */
    private Channel.OnMessageListener onAssignColorListener = new Channel.OnMessageListener() {
        @Override
        public void onMessage(Message message) {
            if (message != null && message.getData() != null) {
                EventBus.getDefault().post(new AssignColorEvent(message.getData().toString()));
            }
        }
    };

    /**
     * Receive the add track event.
     */
    private Channel.OnMessageListener onRemoveTrackColorListener = new Channel.OnMessageListener() {
        @Override
        public void onMessage(Message message) {
            if (message != null && message.getData() != null) {
                EventBus.getDefault().post(new RemoveTrackEvent(message.getData().toString()));
            }
        }
    };

    /**
     * Sent the data to TV.
     *
     * @param event  the channel event.
     * @param data   the object to sent to TV.
     * @param target the target to receive message.
     */
    private void sendToTV(String event, Object data, String target) {
        if (mMultiscreenApp != null && mMultiscreenApp.isConnected()) {
            mMultiscreenApp.publish(event, data, target);
        }
    }

    /**
     * Register network change listeners.
     */
    private void registerWiFiStateListener() {
        IntentFilter mWiFiStateFilter = new IntentFilter();
        mWiFiStateFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
        mWiFiStateFilter.addAction(android.net.ConnectivityManager.CONNECTIVITY_ACTION);
        App.getInstance().registerReceiver(mWifiStateChangedReceiver, mWiFiStateFilter);
    }

    /**
     * Broadcast receiver for network changes.
     */
    BroadcastReceiver mWifiStateChangedReceiver = new BroadcastReceiver() {

        @Override
        public void onReceive(Context context, Intent intent) {
            if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(intent.getAction())) {
                int extraWifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
                        WifiManager.WIFI_STATE_UNKNOWN);

                switch (extraWifiState) {
                case WifiManager.WIFI_STATE_DISABLED:
                case WifiManager.WIFI_STATE_DISABLING:
                    //WiFi is not available.
                    stopDiscovery();
                    getServiceAdapter().clear();
                    notifyDataChange();
                    break;
                case WifiManager.WIFI_STATE_ENABLED:
                    //Use ConnectivityManager.CONNECTIVITY_ACTION instead.
                    break;
                }
            } else if (android.net.ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
                //WiFi is connected
                startDiscovery();
            }
        }
    };
}