Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a42361d1 authored by Cuong's avatar Cuong Committed by Gerrit Code Review
Browse files

base: External BT GPS

Change-Id: I869320d42f40e8b762b8b27702ea4495403f8fc5
Credit: Cuong <cuongbui@gmail.com>
parent 8c1e7f70
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -3598,6 +3598,11 @@ public final class Settings {
         */
        public static final String ASSISTED_GPS_ENABLED = "assisted_gps_enabled";

        /**
         * External GPS source/device
         * @hide
         */
        public static final String EXTERNAL_GPS_BT_DEVICE = "0";
        /**
         * The Logging ID (a unique 64-bit value) as a hex string.
         * Used as a pseudonymous identifier for logging.
+3 −0
Original line number Diff line number Diff line
@@ -88,4 +88,7 @@ interface ILocationManager

    // for NI support
    boolean sendNiResponse(int notifId, int userResponse);

    // add set gps source
    void setGPSSource(String device);
}
+8 −0
Original line number Diff line number Diff line
@@ -279,6 +279,14 @@ public class LocationManager {
        return provider;
    }

    public void setGPSSource(String device) {
        try {
            mService.setGPSSource(device);
        } catch (RemoteException e) {
            Log.e(TAG, e.getMessage());
        }
    }

    /**
     * Returns a list of the names of all known location providers.  All
     * providers are returned, including ones that are not permitted to be
+42 −8
Original line number Diff line number Diff line
@@ -58,6 +58,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.WorkSource;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
import android.util.PrintWriterPrinter;
@@ -65,6 +66,7 @@ import android.util.PrintWriterPrinter;
import com.android.internal.content.PackageMonitor;
import com.android.internal.location.GpsNetInitiatedHandler;

import com.android.server.location.BTGpsLocationProvider;
import com.android.server.location.GeocoderProxy;
import com.android.server.location.GpsLocationProvider;
import com.android.server.location.LocationProviderInterface;
@@ -479,17 +481,49 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
        }
    }

    private void _loadProvidersLocked() {
        // Attempt to load "real" providers first
        if (GpsLocationProvider.isSupported()) {
            // Create a gps location provider
    public void setGPSSource(String device) {
        synchronized (mLock) {
            if (mGpsLocationProvider != null &&
                    mProvidersByName.containsKey(mGpsLocationProvider.getName())) {
                Slog.i(TAG, "Disable and removing provider " + mGpsLocationProvider.getName());
                mGpsLocationProvider.disable();
                Settings.Secure.setLocationProviderEnabled(mContext.getContentResolver(),
                        LocationManager.GPS_PROVIDER, false);
                removeProvider(mGpsLocationProvider);
                mGpsLocationProvider = null;
            }
            Slog.i(TAG, "Setting GPS Source to: " + device);
            if ("0".equals(device)) {
                if (mGpsLocationProvider != null && !GpsLocationProvider.isSupported())
                    return;
                GpsLocationProvider gpsProvider = new GpsLocationProvider(mContext, this);
                mGpsStatusProvider = gpsProvider.getGpsStatusProvider();
                mNetInitiatedListener = gpsProvider.getNetInitiatedListener();
                addProvider(gpsProvider);
                mGpsLocationProvider = gpsProvider;
            } else {
                BTGpsLocationProvider gpsProvider = new BTGpsLocationProvider(mContext, this);
                mGpsStatusProvider = gpsProvider.getGpsStatusProvider();
                mNetInitiatedListener = null;
                addProvider(gpsProvider);
                mGpsLocationProvider = gpsProvider;
            }
        }
    }

    private void _loadProvidersLocked() {
        // Attempt to load "real" providers first
        // Create a gps location provider based on the setting EXTERNAL_GPS_BT_DEVICE
        String btDevice = Settings.System.getString(mContext.getContentResolver(),
                Settings.Secure.EXTERNAL_GPS_BT_DEVICE);
        if (TextUtils.isEmpty(btDevice)) {
            // default option
            btDevice = "0";
            Settings.System.putString(mContext.getContentResolver(),
                    Settings.Secure.EXTERNAL_GPS_BT_DEVICE, btDevice);
        }
        setGPSSource(btDevice);

        // create a passive location provider, which is always enabled
        PassiveProvider passiveProvider = new PassiveProvider(this);
        addProvider(passiveProvider);
+465 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2011 Cuong Bui
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.server.location;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.UUID;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

public class BTGPSService {
    private static final boolean D = true;
    private static final String TAG = "BTGPSService";
    private static final UUID BT_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
    private final BluetoothAdapter mAdapter;
    private final Handler mHandler;
    private WatchdogThread mWatchdogThread = null;
    private ConnectThread mConnectThread = null;
    private ConnectedThread mConnectedThread = null;
    private final int mMaxNMEABuffer=4096;
    private final char[] buffer = new char[mMaxNMEABuffer];
    int bytes;
    private long refreshRate = 1000;
    private long lastActivity = 0;
    // MAX_ACTIVITY_TIMEOUT * refresh time window should have at least one activity.
    private int MAX_ACTIVITY_TIMEOUT = 5;
    // Maximum connect retry attempt
    private int MAX_RECONNECT_RETRIES = 5;
    // time window for one single connection (ms). socket connect timeout is around 12 sec
    private int MAX_CONNECT_TIMEOUT = 13000;
    // last connected device. is used to auto reconnect.
    private BluetoothDevice lastConnectedDevice=null;

    private int mState = 0;
    // Constants that indicate the current connection state
    public static final int STATE_NONE = 0;       // we're doing nothing
    public static final int STATE_LISTEN = 1;     // now listening for incoming connections
    public static final int STATE_CONNECTING = 2; // now initiating an outgoing connection
    public static final int STATE_CONNECTED = 3;  // now connected to a remote device

    public synchronized void setRefreshRate(long r) {
        refreshRate = r;
    }

    public synchronized long getRefreshRate() {
        return refreshRate;
    }

    public BTGPSService(Handler h) {
        mAdapter = BluetoothAdapter.getDefaultAdapter();
        mHandler = h;
    }

    private void sendMessage(int message, int arg, Object obj) {
        Message m = Message.obtain(mHandler, message);
        m.arg1 = arg;
        m.obj = obj;
        mHandler.sendMessage(m);
    }

    private void handleFailedConnection() {
        if (getServiceState() != STATE_NONE) {
            if (D) Log.d(TAG, "Connection failed with status != 0. try to reconnect");
            connect(lastConnectedDevice);
        } else {
            if (D) Log.d(TAG, "Connection stopped with status = 0.");
        }
    }

    /**
     * Set the current state of the chat connection
     * @param state  An integer defining the current connection state
     */
    private synchronized void setState(int state) {
        if (D) Log.d(TAG, "setState() " + mState + " -> " + state);
        mState = state;
        if (mState == STATE_NONE) {
            sendMessage(BTGpsLocationProvider.GPS_STATUS_UPDATE, 0, null);
        } else if (mState == STATE_CONNECTED) {
            sendMessage(BTGpsLocationProvider.GPS_STATUS_UPDATE, 1, null);
        }
    }

    /**
     * Return the current connection state. */
    public synchronized int getServiceState() {
        return mState;
    }

    /**
     * Start the chat service. Specifically start AcceptThread to begin a
     * session in listening (server) mode. Called by the Activity onResume() */
    public synchronized void start() {

        if (D) Log.d(TAG, "start");
        if (!mAdapter.isEnabled()) {
            setState(STATE_NONE);
            return;
        }
        // Cancel any thread attempting to make a connection
        if (mConnectThread != null) {
            mConnectThread.cancel();
            mConnectThread = null;
        }
        // Cancel any thread currently running a connection
        if (mConnectedThread != null) {
            mConnectedThread.cancel();
            mConnectedThread = null;
        }
        setState(STATE_LISTEN);
    }

    /**
     * Start the ConnectThread to initiate a connection to a remote device.
     * @param device  The BluetoothDevice to connect
     */
    public synchronized boolean connect(BluetoothDevice device) {
        lastConnectedDevice = device;
        if (D) Log.d(TAG, "connect to: " + device);
        // Cancel any thread attempting to make a connection
        if (mConnectThread != null) {
            mConnectThread.cancel();
            mConnectThread = null;
        }
        if (mWatchdogThread != null) {
            mWatchdogThread.cancel();
            mWatchdogThread = null;
        }
        // Cancel any thread currently running a connection
        if (mConnectedThread != null) {
            mConnectedThread.cancel();
            mConnectedThread = null;
        }
        // Helper thread that monitors and retries to connect after time out
        mWatchdogThread = new WatchdogThread(device);
        mWatchdogThread.start();
        return true;
    }

    /**
     * Start the ConnectedThread to begin managing a Bluetooth connection
     * @param socket  The BluetoothSocket on which the connection was made
     * @param device  The BluetoothDevice that has been connected
     */
    public synchronized void connected(BluetoothSocket socket) {
        // reset connect thread
        if (mConnectThread != null) mConnectThread = null;

        // kill watchdog, since we are connected
        if (mWatchdogThread != null) {
            mWatchdogThread.cancel();
            mWatchdogThread = null;
        }
        // Cancel any thread currently running a connection
        if (mConnectedThread != null) {
            mConnectedThread.cancel();
            mConnectedThread = null;
        }

        // Start the thread to manage the connection and perform transmissions
        mConnectedThread = new ConnectedThread(socket);
        mConnectedThread.start();
        setState(STATE_CONNECTED);
    }

    /**
     * Stop all threads
     */
    public synchronized void stop() {
        if (D) Log.d(TAG, "Stopping btsvc, Set state to None");
        setState(STATE_NONE);

        if (mWatchdogThread != null) {
            if (D) Log.d(TAG, "Cancelling watchdog thread");
            mWatchdogThread.cancel();
            mWatchdogThread = null;
        }

        if (mConnectThread != null) {
            if (D) Log.d(TAG, "Cancelling connect thread");
            mConnectThread.cancel();
            mConnectThread = null;
        }
        if (mConnectedThread != null) {
            if (D) Log.d(TAG, "Cancelling connected thread");
            mConnectedThread.cancel();
            mConnectedThread = null;
        }
    }

    /**
     * Write to the ConnectedThread in an unsynchronized manner
     * @param out The bytes to write
     * @see ConnectedThread#write(byte[])
     */
    public void write(byte[] out) {
        // Create temporary object
        ConnectedThread r;
        // Synchronize a copy of the ConnectedThread
        synchronized (this) {
            if (mState != STATE_CONNECTED) return;
            r = mConnectedThread;
        }
        r.write(out);
    }

    /**
     * This thread runs while attempting to make an outgoing connection
     * with a device. It runs straight through; the connection either
     * succeeds or fails.
     */
    private class ConnectThread extends Thread {
        private BluetoothSocket mmSocket;
        private final BluetoothDevice mmDevice;
        private String mSocketType;

        public ConnectThread(BluetoothDevice device) {
            mmDevice = device;
        }

        private void closeSocket() {
            if (D) Log.d(TAG, getId()+":close socket");
            if (mmSocket == null) {
                Log.e(TAG, getId()+":Socket not ready. Aborting Close");
                return;
            }

            try {
                mmSocket.close();
                mmSocket = null;
            } catch (IOException e) {
                Log.e(TAG, getId()+":close() of connect " + mSocketType + " socket failed", e);
            }
        }

        public void run() {
            Log.i(TAG, getId() + ":begin mConnectThread");
            BluetoothSocket tmp = null;
            // Always cancel discovery because it will slow down a connection

            try {
                tmp = mmDevice.createRfcommSocketToServiceRecord(BT_UUID);
            } catch (IOException e) {
                Log.e(TAG, "Socket create() failed", e);
                return;
            }
            mmSocket = tmp;
            // Make a connection to the BluetoothSocket
            if (mAdapter.isEnabled()) mAdapter.cancelDiscovery();
            try {
                // This is a blocking call and will only return on a
                // successful connection or an exception
                if (D)  Log.d(TAG, getId() + ":Connecting to socket...");
                mmSocket.connect();
                if (D) Log.d(TAG, "connected with remote device: "
                        + mmDevice.getName() + " at address " + mmDevice.getAddress());
                connected(mmSocket);
            } catch (IOException e) {
                Log.w(TAG, getId() + ":connect failed.", e);
                return;
            }
        }

        public synchronized void cancel() {
            closeSocket();
        }
    }

    /**
     * This thread runs during a connection with a remote device.
     * It handles all incoming and outgoing transmissions.
     */
    private class ConnectedThread extends Thread {
        private BluetoothSocket mmSocket;
        private InputStream mmInStream;
        private OutputStream mmOutStream;
        private boolean cancelled = false;

        private void closeSocket() {
            if (D) Log.d(TAG, getId()+":close socket");
            if (mmSocket == null) {
                Log.e(TAG, getId()+":Socket not ready. Aborting Close");
                return;
            }
            try {
                mmSocket.close();
                mmSocket = null;
            } catch (IOException e) {
                Log.e(TAG, getId()+": close() of connect socket failed", e);
            }
        }

        public ConnectedThread(BluetoothSocket socket) {
            Log.d(TAG, getId() + ":begin ConnectedThread");
            mmSocket = socket;
            InputStream tmpIn = null;
            OutputStream tmpOut = null;

            // Get the BluetoothSocket input and output streams
            try {
                tmpIn = socket.getInputStream();
                tmpOut = socket.getOutputStream();
            } catch (IOException e) {
                Log.e(TAG, "temp sockets not created", e);
            }
            mmInStream = tmpIn;
            mmOutStream = tmpOut;
        }

        public void run() {
            if (mmSocket == null || mmInStream == null) {
                Log.e(TAG, "Input stream or socket is null. Aborting thread");
                return;
            }
            if (D) Log.d(TAG, getId() + ":BEGIN mConnectedThread");
            java.util.Arrays.fill(buffer, (char) ' ');
            // reset refresh rate to 1000
            refreshRate = 1000;
            lastActivity = 0;
            BufferedReader reader = new BufferedReader(new InputStreamReader(mmInStream));
            // Keep listening to the InputStream while connected
            while (true) {
                try {
                    if (reader.ready()) {
                        bytes = reader.read(buffer, 0, mMaxNMEABuffer);
                        Message msg = mHandler.obtainMessage(
                                BTGpsLocationProvider.GPS_DATA_AVAILABLE,buffer);
                        lastActivity = System.currentTimeMillis();
                        msg.arg1 = bytes;
                        mHandler.sendMessage(msg);
                    }
                    if (lastActivity != 0 && (System.currentTimeMillis() - lastActivity)  >
                            MAX_ACTIVITY_TIMEOUT*refreshRate) {
                        Log.w(TAG, getId() + ":BT activity timeout.");
                        closeSocket();
                        handleFailedConnection();
                        return;
                    }
                    try {
                        // get default sleep time
                        Thread.sleep(getRefreshRate());
                    } catch (InterruptedException e) {
                        if (cancelled) {
                            closeSocket();
                            return;
                        }
                    }
                } catch (IOException e) {
                    Log.w(TAG, getId() + ":disconnected.", e);
                    closeSocket();
                    handleFailedConnection();
                    return;
                }
            }
        }

        /**
         * Write to the connected OutStream.
         * @param buffer  The bytes to write
         */
        public void write(byte[] buffer) {
            try {
                mmOutStream.write(buffer);
                mmOutStream.flush();
            } catch (IOException e) {
                Log.e(TAG, "Exception during write", e);
            }
        }

        public void cancel() {
            try {
                if (mmSocket == null) {
                    Log.e(TAG, "Input stream null. Aborting Cacnel");
                    return;
                }
                mmSocket.close();
            } catch (IOException e) {
                Log.e(TAG, "close() of connect socket failed", e);
            } finally {
                cancelled = true;
                interrupt();
            }
        }
    }
    /*
     * Thread that starts the connection thread an monitors it.
     * Thread will be cancelled if timeot occurs
     */
    private class WatchdogThread extends Thread {
        private final BluetoothDevice btdevice;
        private int retries = 0;
        private boolean sleep = false;
        private boolean cancelled = false;

        public WatchdogThread(BluetoothDevice dev) {
            btdevice = dev;
        }

        public void run() {
            while(retries < MAX_RECONNECT_RETRIES) {
                if (mConnectThread != null) {
                    mConnectThread.cancel();
                    mConnectThread = null;
                }
                if (mConnectedThread != null) {
                    mConnectedThread.cancel();
                    mConnectedThread = null;
                }

                mConnectThread = new ConnectThread(btdevice);
                mConnectThread.start();
                setState(STATE_CONNECTING);
                // monitor connection and cancel if timeout
                if (D) Log.d(TAG, getId() + ":Waiting " + MAX_CONNECT_TIMEOUT
                        + " (ms) for service to connect...");
                try {
                    sleep = true;
                    Thread.sleep(MAX_CONNECT_TIMEOUT);
                    sleep = false;
                    if (D) Log.d(TAG, getId() + ":Connecting timeout.");
                } catch (InterruptedException e) {
                    if (D) Log.d(TAG, getId() + ":Watchdog interrupted. probably by cancel.");
                }
                if (getServiceState() == STATE_CONNECTED) {
                    if (D) Log.d(TAG, getId() + ":Connected. aborting watchdog");
                    return;
                }
                if (cancelled) {
                    if (D) Log.d(TAG, getId() + ":Cancelled. aborting watchdog");
                    return;
                }
                retries++;
            }
            // max timeout, so stopping service
            if (D) Log.d(TAG, getId() + ":Max connection retries exceeded. stopping services.");
            BTGPSService.this.stop();
        }

        public void cancel() {
            cancelled = true;
            if (sleep) interrupt();
        }
    }
}
Loading