<commit_before>/*
 * TeleStax, Open Source Cloud Communications
 * Copyright 2011-2015, Telestax Inc and individual contributors
 * by the @authors tag.
 *
 * This program is free software: you can redistribute it and/or modify
 * under the terms of the GNU Affero 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 *
 * For questions related to commercial use licensing, please contact <EMAIL>.
 *
 */

package org.mobicents.restcomm.android.client.sdk;

import java.util.HashMap;
import java.util.Map;

import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.os.Handler;
import org.mobicents.restcomm.android.sipua.RCLogger;

import org.mobicents.restcomm.android.sipua.SipProfile;
import org.mobicents.restcomm.android.sipua.SipUADeviceListener;
import org.mobicents.restcomm.android.sipua.impl.DeviceImpl;
import org.mobicents.restcomm.android.sipua.impl.SipEvent;

/**
 *  RCDevice Represents an abstraction of a communications device able to make and receive calls, send and receive messages etc. Remember that
 *  in order to be notified of RestComm Client events you need to set a listener to RCDevice and implement the applicable methods, and also 'register'
 *  to the applicable intents by calling RCDevice.setPendingIntents() and provide one intent for whichever activity will be receiving calls and another
 *  intent for the activity receiving messages.
 *  If you want to initiate a media connection towards another party you use RCDevice.connect() which returns an RCConnection object representing
 *  the new outgoing connection. From then on you can act on the new connection by applying RCConnection methods on the handle you got from RCDevice.connect().
 *  If there is an incoming connection you will be receiving an intent with action 'RCDevice.INCOMING_CALL'. At that point you can use RCConnection methods to
 *  accept or reject the connection.
 *
 *  As far as instant messages are concerned you can send a message using RCDevice.sendMessage() and you will be notified of an incoming message
 *  through an intent with action 'RCDevice.INCOMING_MESSAGE'.
 *
 *  @see RCConnection
 */

public class RCDevice extends BroadcastReceiver implements SipUADeviceListener  {
    /**
     * @abstract Device state
     */
    static DeviceState state;
    /**
     * @abstract Device capabilities (<b>Not Implemented yet</b>)
     */
    HashMap<DeviceCapability, Object> capabilities;
    /**
     * @abstract Listener that will be receiving RCDevice events described at RCDeviceListener
     */
    RCDeviceListener listener;
    /**
     * @abstract Is sound for incoming connections enabled
     */
    boolean incomingSoundEnabled;
    /**
     * @abstract Is sound for outgoing connections enabled
     */
    boolean outgoingSoundEnabled;
    /**
     * @abstract Is sound for disconnect enabled
     */
    boolean disconnectSoundEnabled;

    /**
     * Device state
     */
    public enum DeviceState {
        OFFLINE, /** Device is offline */
        READY, /** Device is ready to make and receive connections */
        BUSY,  /** Device is busy */
    }

    /**
     * Device capability (<b>Not Implemented yet</b>)
     */
    public enum DeviceCapability {
        INCOMING,
        OUTGOING,
        EXPIRATION,
        ACCOUNT_SID,
        APPLICATION_SID,
        APPLICATION_PARAMETERS,
        CLIENT_NAME,
    }

    private static final String TAG = "RCDevice";
    //private static boolean online = false;
    public static String OUTGOING_CALL = "ACTION_OUTGOING_CALL";
    public static String INCOMING_CALL = "ACTION_INCOMING_CALL";
    public static String OPEN_MESSAGE_SCREEN = "ACTION_OPEN_MESSAGE_SCREEN";
    public static String INCOMING_MESSAGE = "ACTION_INCOMING_MESSAGE";
    public static String INCOMING_MESSAGE_TEXT = "INCOMING_MESSAGE_TEXT";
    public static String INCOMING_MESSAGE_PARAMS = "INCOMING_MESSAGE_PARAMS";
    public static String EXTRA_DID = "com.telestax.restcomm_messenger.DID";
    public static String EXTRA_VIDEO_ENABLED = "com.telestax.restcomm_messenger.VIDEO_ENABLED";
    public static String EXTRA_SDP = "com.telestax.restcomm_messenger.SDP";
    //public static String EXTRA_DEVICE = "com.telestax.restcomm.android.client.sdk.extra-device";
    //public static String EXTRA_CONNECTION = "com.telestax.restcomm.android.client.sdk.extra-connection";
    HashMap<String, Object> parameters;
    PendingIntent pendingCallIntent;
    PendingIntent pendingMessageIntent;
    private RCConnection incomingConnection;
    private RCDeviceListener.RCConnectivityStatus reachabilityState = RCDeviceListener.RCConnectivityStatus.RCConnectivityStatusNone;
    private SipProfile sipProfile = null;

    /**
     * Initialize a new RCDevice object
     *
     * @param parameters RCDevice parameters
     * @param deviceListener  Listener of RCDevice
     */
    protected RCDevice(HashMap<String, Object> parameters, RCDeviceListener deviceListener) {
        RCLogger.i(TAG, "RCDevice(): " + parameters.toString());
        //this.updateCapabilityToken(capabilityToken);
        this.listener = deviceListener;

        // TODO: check if those headers are needed
        HashMap<String, String> customHeaders = new HashMap<>();
        state = DeviceState.OFFLINE;

        // register broadcast receiver for reachability
        Context context = RCClient.getContext();
        IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
        context.registerReceiver(this, filter);

        // initialize JAIN SIP if we have connectivity
        this.parameters = parameters;
        reachabilityState = DeviceImpl.checkReachability(RCClient.getContext());

        boolean connectivity = false;
        if (reachabilityState != RCDeviceListener.RCConnectivityStatus.RCConnectivityStatusNone) {
            connectivity = true;
        }
        initializeSignalling(connectivity);
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        onReachabilityChanged(DeviceImpl.checkReachability(RCClient.getContext()));
    }

    private void initializeSignalling(boolean connectivity)
    {
        RCLogger.i(TAG, "initializeSignalling()");
        sipProfile = new SipProfile();
        updateSipProfile(parameters);
        DeviceImpl deviceImpl = DeviceImpl.GetInstance();
        deviceImpl.Initialize(RCClient.getContext(), sipProfile, connectivity);
        DeviceImpl.GetInstance().sipuaDeviceListener = this;
        // register after initialization
        if (connectivity) {
            if (!parameters.containsKey("pref_proxy_domain") ||
                    parameters.containsKey("pref_proxy_domain") && parameters.get("pref_proxy_domain").equals("")) {
                // registrarless; we can transition to ready right away (i.e. without waiting for Restcomm to reply to REGISTER)
                state = DeviceState.READY;
            }
            else {
                DeviceImpl.GetInstance().Register();
            }
            //state = DeviceState.READY;
        }
    }

    private void onReachabilityChanged(final RCDeviceListener.RCConnectivityStatus newState)
    {
        if (newState == RCDeviceListener.RCConnectivityStatus.RCConnectivityStatusNone && state != DeviceState.OFFLINE) {
            RCLogger.w(TAG, "Reachability changed; no connectivity");
            DeviceImpl.GetInstance().unbind();
            state = DeviceState.OFFLINE;
            reachabilityState = newState;
            this.listener.onConnectivityUpdate(this, newState);
            return;
        }

        // old state wifi and new state mobile or the reverse; need to shutdown and restart network facilities
        if ((reachabilityState == RCDeviceListener.RCConnectivityStatus.RCConnectivityStatusWiFi && newState == RCDeviceListener.RCConnectivityStatus.RCConnectivityStatusCellular) ||
                (reachabilityState == RCDeviceListener.RCConnectivityStatus.RCConnectivityStatusCellular && newState == RCDeviceListener.RCConnectivityStatus.RCConnectivityStatusWiFi)) {
            if (state != DeviceState.OFFLINE) {
                RCLogger.w(TAG, "Reachability action: switch between wifi and mobile. Device state: " + state);
                // refresh JAIN networking facilities so that we use the new available interface
                DeviceImpl.GetInstance().RefreshNetworking();
                reachabilityState = newState;<commit_msg>Fixed #245: Sometimes 2 registrations go out when Messenger comes up<commit_after>/*
 * TeleStax, Open Source Cloud Communications
 * Copyright 2011-2015, Telestax Inc and individual contributors
 * by the @authors tag.
 *
 * This program is free software: you can redistribute it and/or modify
 * under the terms of the GNU Affero 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 *
 * For questions related to commercial use licensing, please contact <EMAIL>.
 *
 */

package org.mobicents.restcomm.android.client.sdk;

import java.util.HashMap;
import java.util.Map;

import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.os.Handler;
import org.mobicents.restcomm.android.sipua.RCLogger;

import org.mobicents.restcomm.android.sipua.SipProfile;
import org.mobicents.restcomm.android.sipua.SipUADeviceListener;
import org.mobicents.restcomm.android.sipua.impl.DeviceImpl;
import org.mobicents.restcomm.android.sipua.impl.SipEvent;

/**
 *  RCDevice Represents an abstraction of a communications device able to make and receive calls, send and receive messages etc. Remember that
 *  in order to be notified of RestComm Client events you need to set a listener to RCDevice and implement the applicable methods, and also 'register'
 *  to the applicable intents by calling RCDevice.setPendingIntents() and provide one intent for whichever activity will be receiving calls and another
 *  intent for the activity receiving messages.
 *  If you want to initiate a media connection towards another party you use RCDevice.connect() which returns an RCConnection object representing
 *  the new outgoing connection. From then on you can act on the new connection by applying RCConnection methods on the handle you got from RCDevice.connect().
 *  If there is an incoming connection you will be receiving an intent with action 'RCDevice.INCOMING_CALL'. At that point you can use RCConnection methods to
 *  accept or reject the connection.
 *
 *  As far as instant messages are concerned you can send a message using RCDevice.sendMessage() and you will be notified of an incoming message
 *  through an intent with action 'RCDevice.INCOMING_MESSAGE'.
 *
 *  @see RCConnection
 */

public class RCDevice extends BroadcastReceiver implements SipUADeviceListener  {
    /**
     * @abstract Device state
     */
    static DeviceState state;
    /**
     * @abstract Device capabilities (<b>Not Implemented yet</b>)
     */
    HashMap<DeviceCapability, Object> capabilities;
    /**
     * @abstract Listener that will be receiving RCDevice events described at RCDeviceListener
     */
    RCDeviceListener listener;
    /**
     * @abstract Is sound for incoming connections enabled
     */
    boolean incomingSoundEnabled;
    /**
     * @abstract Is sound for outgoing connections enabled
     */
    boolean outgoingSoundEnabled;
    /**
     * @abstract Is sound for disconnect enabled
     */
    boolean disconnectSoundEnabled;

    /**
     * Device state
     */
    public enum DeviceState {
        OFFLINE, /** Device is offline */
        READY, /** Device is ready to make and receive connections */
        BUSY,  /** Device is busy */
    }

    /**
     * Device capability (<b>Not Implemented yet</b>)
     */
    public enum DeviceCapability {
        INCOMING,
        OUTGOING,
        EXPIRATION,
        ACCOUNT_SID,
        APPLICATION_SID,
        APPLICATION_PARAMETERS,
        CLIENT_NAME,
    }

    private static final String TAG = "RCDevice";
    //private static boolean online = false;
    public static String OUTGOING_CALL = "ACTION_OUTGOING_CALL";
    public static String INCOMING_CALL = "ACTION_INCOMING_CALL";
    public static String OPEN_MESSAGE_SCREEN = "ACTION_OPEN_MESSAGE_SCREEN";
    public static String INCOMING_MESSAGE = "ACTION_INCOMING_MESSAGE";
    public static String INCOMING_MESSAGE_TEXT = "INCOMING_MESSAGE_TEXT";
    public static String INCOMING_MESSAGE_PARAMS = "INCOMING_MESSAGE_PARAMS";
    public static String EXTRA_DID = "com.telestax.restcomm_messenger.DID";
    public static String EXTRA_VIDEO_ENABLED = "com.telestax.restcomm_messenger.VIDEO_ENABLED";
    public static String EXTRA_SDP = "com.telestax.restcomm_messenger.SDP";
    //public static String EXTRA_DEVICE = "com.telestax.restcomm.android.client.sdk.extra-device";
    //public static String EXTRA_CONNECTION = "com.telestax.restcomm.android.client.sdk.extra-connection";
    HashMap<String, Object> parameters;
    PendingIntent pendingCallIntent;
    PendingIntent pendingMessageIntent;
    private RCConnection incomingConnection;
    private RCDeviceListener.RCConnectivityStatus reachabilityState = RCDeviceListener.RCConnectivityStatus.RCConnectivityStatusNone;
    private SipProfile sipProfile = null;

    /**
     * Initialize a new RCDevice object
     *
     * @param parameters RCDevice parameters
     * @param deviceListener  Listener of RCDevice
     */
    protected RCDevice(HashMap<String, Object> parameters, RCDeviceListener deviceListener) {
        RCLogger.i(TAG, "RCDevice(): " + parameters.toString());
        //this.updateCapabilityToken(capabilityToken);
        this.listener = deviceListener;

        // TODO: check if those headers are needed
        HashMap<String, String> customHeaders = new HashMap<>();
        state = DeviceState.OFFLINE;

        // register broadcast receiver for reachability
        Context context = RCClient.getContext();
        IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
        context.registerReceiver(this, filter);

        // initialize JAIN SIP if we have connectivity
        this.parameters = parameters;
        reachabilityState = DeviceImpl.checkReachability(RCClient.getContext());

        boolean connectivity = false;
        if (reachabilityState != RCDeviceListener.RCConnectivityStatus.RCConnectivityStatusNone) {
            connectivity = true;
        }
        initializeSignalling(connectivity);
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        onReachabilityChanged(DeviceImpl.checkReachability(RCClient.getContext()));
    }

    private void initializeSignalling(boolean connectivity)
    {
        RCLogger.i(TAG, "initializeSignalling()");
        sipProfile = new SipProfile();
        updateSipProfile(parameters);
        DeviceImpl deviceImpl = DeviceImpl.GetInstance();
        deviceImpl.Initialize(RCClient.getContext(), sipProfile, connectivity);
        DeviceImpl.GetInstance().sipuaDeviceListener = this;
        // register after initialization
        if (connectivity) {
            if (!parameters.containsKey("pref_proxy_domain") ||
                    parameters.containsKey("pref_proxy_domain") && parameters.get("pref_proxy_domain").equals("")) {
                // registrarless; we can transition to ready right away (i.e. without waiting for Restcomm to reply to REGISTER)
                state = DeviceState.READY;
            }
            else {
                DeviceImpl.GetInstance().Register();
            }
            //state = DeviceState.READY;
        }
    }

    private void onReachabilityChanged(final RCDeviceListener.RCConnectivityStatus newState)
    {
        if (newState == reachabilityState) {
            RCLogger.w(TAG, "Reachability event, but remained the same: " + newState);
            return;
        }

        if (newState == RCDeviceListener.RCConnectivityStatus.RCConnectivityStatusNone && state != DeviceState.OFFLINE) {
            RCLogger.w(TAG, "Reachability changed; no connectivity");
            DeviceImpl.GetInstance().unbind();
            state = DeviceState.OFFLINE;
            reachabilityState = newState;
            this.listener.onConnectivityUpdate(this, newState);
            return;
        }

        // old state wifi and new state mobile or the reverse; need to shutdown and restart network facilities
        if ((reachabilityState == RCDeviceListener.RCConnectivityStatus.RCConnectivityStatusWiFi && newState == RCDeviceListener.RCConnectivityStatus.RCConnectivityStatusCellular) ||
                (reachabilityState == RCDeviceListener.RCConnectivityStatus.RCConnectivityStatusCellular && newState == RCDeviceListener.RCConnectivityStatus.RCConnectivityStatusWiFi)) {
            if (state != DeviceState.OFFLINE) {
                RCLogger.w(TAG, "Reachability action: switch between wifi and mobile. Device state: " + state);
                // refresh JAIN networking facilities so that we use the new available interface
                DeviceImpl.GetInstance().RefreshNetworking();
                reachabilityState = newState;
{'fraction_non_alphanumeric': 0.07613191118850675, 'fraction_numerical': 0.001197213757074445, 'mean_word_length': 3.3321546440358323, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '685133', 'n_tokens_mistral': 4721, 'n_tokens_neox': 4748, 'n_words': 1475}