com.amti.vela.bluetoothlegatt.bluetooth.DeviceScanActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.amti.vela.bluetoothlegatt.bluetooth.DeviceScanActivity.java

Source

/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * 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.amti.vela.bluetoothlegatt.bluetooth;

import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.content.ClipData;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.support.v4.content.ContextCompat;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Toast;

import com.amti.vela.bluetoothlegatt.CustomListAdapter;
import com.amti.vela.bluetoothlegatt.NotificationService;
import com.amti.vela.bluetoothlegatt.Preferences;
import com.amti.vela.bluetoothlegatt.R;
import com.amti.vela.bluetoothlegatt.MainActivity;
import com.amti.vela.bluetoothlegatt.SettingsActivity;

import java.util.ArrayList;

/**
 * Activity for scanning and displaying available Bluetooth LE devices.
 */
public class DeviceScanActivity extends AppCompatActivity {
    private final static String TAG = DeviceScanActivity.class.getSimpleName();

    private static final int REQUEST_ENABLE_BT = 1;
    private static final long SCAN_PERIOD = 10000; // Stops scanning after 10 seconds.

    public static final String NOTIFICATION_SETTINGS_PACKAGE = "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS";

    private BluetoothAdapter mBluetoothAdapter;
    private boolean mScanning;
    private Handler leScanHandler;

    private ArrayList<BluetoothDevice> mLeDevicesList;
    private CustomListAdapter devicesAdapter;
    ListView deviceListView;
    SwipeRefreshLayout swipeContainer;

    boolean disableActionButton = false;

    boolean enableBtFlag;
    boolean checkedBt;

    AlertDialog.Builder notificationEnableDialog;

    DialogInterface.OnClickListener notificationDialogClickListener = new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            switch (which) {
            case DialogInterface.BUTTON_POSITIVE:
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
                    startActivity(new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS));
                } else {
                    startActivity(new Intent(NOTIFICATION_SETTINGS_PACKAGE));
                }
                break;

            case DialogInterface.BUTTON_NEGATIVE:
                Toast.makeText(DeviceScanActivity.this,
                        "Some parts of this app will be disabled. You can manually enable this in the settings app under Notifications -> Notification Access",
                        Toast.LENGTH_LONG).show();
                break;
            }
        }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_device_search);

        //have to request location permissions for marshmallow to do bluetooth
        if (Build.VERSION.SDK_INT >= 23) {
            if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
                    && checkSelfPermission(
                            Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                requestPermissions(new String[] { Manifest.permission.ACCESS_FINE_LOCATION,
                        Manifest.permission.ACCESS_COARSE_LOCATION }, 1);

            } else {

            }
        }

        initGui();

        btInit();

        autoConnectIfEnabled();
    }

    void autoConnectIfEnabled() {
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
        boolean isAutoConnect = prefs.getBoolean(Preferences.PREFS_AUTO_CONNECT_KEY, false);
        String deviceString = prefs.getString(Preferences.PREFS_DEVICE_KEY, "");
        if (isAutoConnect && deviceString.split("\n").length == 2 && mBluetoothAdapter.isEnabled()) {
            final Intent intent = new Intent(DeviceScanActivity.this, MainActivity.class);
            intent.putExtra(MainActivity.EXTRAS_DEVICE, deviceString);
            if (mScanning) {
                scanLeDevice(false);
                mScanning = false;
            }
            if (notificationListenerInit())
                ;
            startActivity(intent);
        }
    }

    boolean notificationListenerInit() {
        //start listening to notifications
        startService(new Intent(DeviceScanActivity.this, NotificationService.class));
        //notification service for detecting when new notifications are received
        if (!NotificationService.notificationsBound) {
            notificationEnableDialog.show();
        }
        return NotificationService.notificationsBound;
    }

    boolean isBtCompat() {
        // Use this check to determine whether BLE is supported on the device.  Then you can
        // selectively disable BLE-related features.
        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            Toast.makeText(this, "This device does not support bluetooth", Toast.LENGTH_SHORT).show();
            finish();
            return false;
        }

        // Checks if Bluetooth is supported on the device.
        if (mBluetoothAdapter == null) {
            Toast.makeText(this, "This device does not support bluetooth", Toast.LENGTH_SHORT).show();
            finish();
            return false;
        }

        return true;
    }

    void initGui() {
        leScanHandler = new Handler();

        //widgets
        deviceListView = (ListView) findViewById(R.id.listView);
        swipeContainer = (SwipeRefreshLayout) findViewById(R.id.swipeContainer);

        mLeDevicesList = new ArrayList<BluetoothDevice>();
        devicesAdapter = new CustomListAdapter(this, R.layout.custom_listview_item);
        deviceListView.setAdapter(devicesAdapter);

        // Configure the refreshing colors
        swipeContainer.setColorSchemeResources(R.color.action_bar_light_blue, R.color.swipe_refresh_dark_green,
                android.R.color.holo_orange_light, android.R.color.holo_red_light);

        notificationEnableDialog = new AlertDialog.Builder(DeviceScanActivity.this);
        notificationEnableDialog.setMessage("This app wants to enable notification access in the settings app.")
                .setPositiveButton("OK", notificationDialogClickListener)
                .setNegativeButton("Cancel", notificationDialogClickListener).setCancelable(false);

        //action bar
        Toolbar actionBarToolBar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(actionBarToolBar);
        actionBarToolBar.setTitle("Choose a Device");
        actionBarToolBar.setTitleTextColor(ContextCompat.getColor(this, R.color.action_bar_text_gray));

        //status bar color
        Window window = getWindow();
        window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            window.setStatusBarColor(ContextCompat.getColor(this, R.color.action_bar_dark_blue));
        }

        deviceListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapter, View v, int position, long arg3) {
                BluetoothDevice device = mLeDevicesList.get(position);
                if (device == null)
                    return;
                final Intent intent = new Intent(DeviceScanActivity.this, MainActivity.class);
                String deviceName = device.getName() == null ? "Unknown Device" : device.getName();
                intent.putExtra(MainActivity.EXTRAS_DEVICE, deviceName + "\n" + device.getAddress());
                if (mScanning) {
                    scanLeDevice(false);
                    mScanning = false;
                }
                if (notificationListenerInit())
                    ;
                startActivity(intent);
            }
        });

        swipeContainer.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                // Your code to refresh the list here.
                // Make sure you call swipeContainer.setRefreshing(false)
                // once the bt request has completed successfully
                clearDevices();
                scanLeDevice(true);
            }
        });
    }

    void btInit() {
        // Initializes a Bluetooth adapter.  For API level 18 and above, get a reference to
        // BluetoothAdapter through BluetoothManager.
        final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        mBluetoothAdapter = bluetoothManager.getAdapter();

        //with the swipe refresh, we need to manually set refresh to true through a runnable on app start or it doesn't work
        swipeContainer.post(new Runnable() {
            @Override
            public void run() {
                swipeContainer.setRefreshing(true);
            }
        });
    }

    void setSwipeContainerRefresh(final boolean refresh) {
        swipeContainer.post(new Runnable() {
            @Override
            public void run() {
                swipeContainer.setRefreshing(refresh);
            }
        });
    }

    void clearDevices() {
        mLeDevicesList.clear();
        devicesAdapter.clear();
        devicesAdapter.notifyDataSetChanged();
    }

    private void scanLeDevice(final boolean enable) {
        //pentuple check bt
        if (!enableBtIfNeeded()) {
            //start scanning
            if (enable) {
                // this handler stops the scan after a period of time.
                leScanHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        mScanning = false;
                        disableActionButton = false;
                        setSwipeContainerRefresh(false);
                        mBluetoothAdapter.stopLeScan(mLeScanCallback);
                        Log.e(TAG, "Stopped scan");
                        invalidateOptionsMenu();
                    }
                }, SCAN_PERIOD);

                mScanning = true;
                disableActionButton = true;
                setSwipeContainerRefresh(true);
                mBluetoothAdapter.startLeScan(mLeScanCallback);
                Log.e(TAG, "Started scan");
            }

            //stop scanning
            else {
                mScanning = false;
                disableActionButton = false;
                setSwipeContainerRefresh(false);
                mBluetoothAdapter.stopLeScan(mLeScanCallback);
                Log.e(TAG, "Stopped scan");
            }
            invalidateOptionsMenu();
        }
    }

    // Device scan callback.
    private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {

        @Override
        public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    //for the love of god, do not add the device if it already exists
                    boolean exists = false;
                    for (BluetoothDevice btDevice : mLeDevicesList) {
                        if (device.getAddress().equals(btDevice.getAddress())) {
                            exists = true;
                            break;
                        }
                    }
                    if (!exists) {
                        mLeDevicesList.add(device);
                        if (device.getName() != null)
                            devicesAdapter.add(new ClipData.Item(device.getName() + "\n" + device.getAddress()));
                        else
                            devicesAdapter.add(new ClipData.Item("Unknown Device\n" + device.getAddress()));
                        devicesAdapter.notifyDataSetChanged();
                    }
                }

            });

        }

    };

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_device_search, menu);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            //menu.findItem(R.id.menu_search).getIcon().setTint(ContextCompat.getColor(this, R.color.action_button_dark_blue));
        }

        if (!mScanning) {
            swipeContainer.setRefreshing(false);
        }
        if (disableActionButton)
            menu.findItem(R.id.menu_search).setEnabled(false);
        else
            menu.findItem(R.id.menu_search).setEnabled(true);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.menu_search:
            mLeDevicesList.clear();
            devicesAdapter.clear();
            scanLeDevice(true);
            return true;
        case R.id.menu_settings:
            Intent intent = new Intent(DeviceScanActivity.this, SettingsActivity.class);
            startActivity(intent);
            return true;
        }
        return true;
    }

    @Override
    protected void onPause() {
        super.onPause();
        scanLeDevice(false);
    }

    @Override
    protected void onResume() {
        super.onResume();

        clearDevices();

        if (!checkedBt)
            scanLeDevice(true);
        else {
            scanLeDevice(false);
            checkedBt = false;
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        enableBtFlag = false;
        // User chose not to enable Bluetooth.
        if (requestCode == REQUEST_ENABLE_BT && resultCode == Activity.RESULT_CANCELED) {

        } else if (requestCode == REQUEST_ENABLE_BT && resultCode == Activity.RESULT_OK) {
            if (isBtCompat())
                autoConnectIfEnabled();
        }

        super.onActivityResult(requestCode, resultCode, data);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }

    boolean enableBtIfNeeded() {
        // Ensures Bluetooth is enabled on the device.  If Bluetooth is not currently enabled,
        // fire an intent to display a dialog asking the user to grant permission to enable it.
        if (!mBluetoothAdapter.isEnabled()) {
            //we have enableBtFlag so that we know if we already have one of these dialogs showing
            if (!mBluetoothAdapter.isEnabled() && !enableBtFlag) {
                Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
                startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
                enableBtFlag = true;
                //checkedBt is for when onResume is called when we get back from the enable bt dialog, the user has
                //to manually start the bt search
                checkedBt = true;
            }
        }
        return enableBtFlag;
    }
}