com.javadog.bluetoothproximitylock.BluetoothFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.javadog.bluetoothproximitylock.BluetoothFragment.java

Source

/*
   Copyright 2014 Cullin Moran
    
   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.javadog.bluetoothproximitylock;

import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.Fragment;
import android.app.admin.DevicePolicyManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.Spinner;
import android.widget.Switch;
import android.widget.TextView;

import com.javadog.bluetoothproximitylock.helpers.BetterCompoundButton;
import com.javadog.bluetoothproximitylock.helpers.BluetoothManager;
import com.javadog.bluetoothproximitylock.helpers.DeviceLockManager;

import java.util.ArrayList;
import java.util.Set;

/**
 * A page for configuring Bluetooth options.
 */
public class BluetoothFragment extends Fragment
        implements CompoundButton.OnCheckedChangeListener, AdapterView.OnItemSelectedListener {
    public final static String PREF_BT_DEVICE_ADDRESS = "btDeviceAddress";
    public final static String PREF_LOCK_DISTANCE = "lockDistance";
    public final static String PREF_INSTANT_LOCK = "instantLock";
    public final static String PREF_REFRESH_INTERVAL = "refreshInterval";
    final static int REQUEST_CODE_ENABLE_ADMIN = 984;
    final static int REQUEST_CODE_ENABLE_BT = 873;

    protected static LocalBroadcastReceiver ssReceiver;
    protected static BetterCompoundButton<Switch> serviceToggle;
    protected static TextView signalStrengthView;
    protected static Spinner deviceChooser;
    protected Set<BluetoothDevice> devicesSet;
    protected static Spinner lockDistance;
    protected static BetterCompoundButton<CheckBox> lockInstantly;
    protected static Spinner refreshIntervalSpinner;
    protected static long refreshInterval;
    protected boolean serviceBound;
    protected static SharedPreferences userPrefs;
    protected BluetoothStateReceiver btStateReceiver;
    private ServiceConnection serviceConnection;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.bt_fragment, container, false);
    }

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

        initialize();
        loadUserPreferences();
        setupClickListeners();
        bindToService();
    }

    /**
     * Initializes object references and performs some other set-up tasks.
     */
    private void initialize() {
        //Get a reference to the user preferences editor
        userPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext());

        //Get fresh references to our views
        serviceToggle = new BetterCompoundButton<>(getActivity(),
                (Switch) getView().findViewById(R.id.button_bt_service_start_stop));
        signalStrengthView = (TextView) getView().findViewById(R.id.bt_signal_strength);
        deviceChooser = (Spinner) getView().findViewById(R.id.bt_device_chooser);
        lockDistance = (Spinner) getView().findViewById(R.id.bt_lock_distances); //TODO: This doesn't do anything yet.
        lockInstantly = new BetterCompoundButton<>(getActivity(),
                (CheckBox) getView().findViewById(R.id.bt_lock_instantly));
        refreshIntervalSpinner = (Spinner) getView().findViewById(R.id.bt_refresh_interval);

        //Get a reference to the local broadcast manager, and specify which intent actions we want to listen for
        LocalBroadcastManager manager = LocalBroadcastManager.getInstance(getActivity().getApplicationContext());
        IntentFilter filter = new IntentFilter();
        filter.addAction(SignalReaderService.ACTION_SIGNAL_STRENGTH_UPDATE);
        filter.addAction(SignalReaderService.ACTION_UNBIND_SERVICE);

        //Instantiate the ssReceiver if it's not already, then register it with the broadcast manager
        if (ssReceiver == null) {
            ssReceiver = new LocalBroadcastReceiver();
        }
        manager.registerReceiver(ssReceiver, filter);

        //Check whether device admin privileges are active, and show a dialog if not
        DevicePolicyManager dpm = (DevicePolicyManager) getActivity()
                .getSystemService(Context.DEVICE_POLICY_SERVICE);
        if (!dpm.isAdminActive(new ComponentName(getActivity().getApplicationContext(), DeviceLockManager.class))) {
            AdminDialogFragment adminDialogFragment = new AdminDialogFragment();
            adminDialogFragment.setCancelable(false);
            adminDialogFragment.show(getFragmentManager(), "needsAdmin");
        }

        populateBtDevices();

        //Start the device chooser in a disabled state if Bluetooth is disabled
        if (BluetoothAdapter.getDefaultAdapter().isEnabled()) {
            deviceChooser.setEnabled(true);
        } else {
            deviceChooser.setEnabled(false);
        }

        //Register a listener with the system to get updates about changes to Bluetooth state
        if (btStateReceiver == null) {
            btStateReceiver = new BluetoothStateReceiver();
        }
        IntentFilter btFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
        getActivity().registerReceiver(btStateReceiver, btFilter);

        /**
         * Will attach the Activity to the Service as soon as the service is started.
         */
        serviceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
                serviceBound = true;
                updateBtServiceUI();
            }

            @Override
            public void onServiceDisconnected(ComponentName componentName) {
                //This should never be called because our service resides in the same process.
            }
        };
    }

    /**
     * Binds to the service if it's running. If it's not, then we'll still get a callback when it's started.
     */
    private void bindToService() {
        Intent bindService = new Intent(getActivity().getApplicationContext(), SignalReaderService.class);

        //Passing 0 as the flag will prevent the service from being started if it hasn't been already.
        if (SignalReaderService.isServiceRunning()) {
            getActivity().bindService(bindService, serviceConnection, 0);
        }
    }

    /**
     * Unbinds from the service.
     */
    public void unbindFromService() {
        if (getActivity() != null && serviceBound) {
            getActivity().unbindService(serviceConnection);
            serviceBound = false;
        }
        updateBtServiceUI();
    }

    protected void populateBtDevices() {
        //Get a Set of all paired bluetooth devices, convert to array
        BluetoothManager.refreshBtDevices();
        devicesSet = BluetoothManager.getAllBtDevices();
        ArrayList<String> devices = new ArrayList<>();
        for (BluetoothDevice b : devicesSet) {
            devices.add(b.getName() + " (" + b.getAddress() + ")");
        }

        //Set the adapter for the device spinner
        deviceChooser.setAdapter(new ArrayAdapter<>(getActivity().getApplicationContext(),
                android.R.layout.simple_spinner_dropdown_item, devices));
    }

    /**
     * Gets previously-set user prefs from Android and sets UI elements appropriately.
     */
    private void loadUserPreferences() {
        //Retrieve chosen device by bluetooth address
        BluetoothDevice[] devices = devicesSet.toArray(new BluetoothDevice[devicesSet.size()]);
        if (!userPrefs.getString(PREF_BT_DEVICE_ADDRESS, "abc").equals("none")) {
            for (int i = 0; i < devices.length; i++) {
                if (devices[i].getAddress().equals(userPrefs.getString(PREF_BT_DEVICE_ADDRESS, "none"))) {
                    deviceChooser.setSelection(i);
                    BluetoothManager.setSelectedDevice(devices[i]);
                    break;
                }
            }
        }
        lockDistance.setSelection(userPrefs.getInt(PREF_LOCK_DISTANCE, 1));
        lockInstantly.silentlySetChecked(userPrefs.getBoolean(PREF_INSTANT_LOCK, false));
        refreshIntervalSpinner.setSelection(userPrefs.getInt(PREF_REFRESH_INTERVAL, 1));

        //Update internal copy of refresh interval based on the value in the spinner
        refreshInterval = interpretRefreshSpinner(refreshIntervalSpinner.getSelectedItemPosition());
    }

    @Override
    public void onPause() {
        super.onPause();

        //Unregister our LocalBroadcastReceiver
        LocalBroadcastManager manager = LocalBroadcastManager.getInstance(getActivity().getApplicationContext());
        manager.unregisterReceiver(ssReceiver);

        //Unregister the BluetoothStateReceiver
        getActivity().unregisterReceiver(btStateReceiver);

        unbindFromService();
    }

    private void setupClickListeners() {
        //Toggle switches
        serviceToggle.setOnCheckedChangeListener(this);
        lockInstantly.setOnCheckedChangeListener(this);

        Spinner[] spinners = { deviceChooser, lockDistance, refreshIntervalSpinner };
        for (Spinner spinner : spinners) {
            spinner.setOnItemSelectedListener(this);
        }
    }

    /**
     * Disables the view(s) with the specified ID(s).
     *
     * @param views The views to be disabled.
     */
    protected void disableUiElement(View... views) {
        for (View v : views) {
            v.setEnabled(false);
        }
    }

    /**
     * Enables the view(s) with the specified ID(s).
     *
     * @param views The views to be enabled.
     */
    protected void enableUiElement(View... views) {
        for (View v : views) {
            v.setEnabled(true);
        }
    }

    /**
     * Enables/disables the bluetooth service.
     *
     * @param on On?
     */
    private void toggleBtService(boolean on) {
        if (on) {
            startBtService();
        } else {
            stopBtService();
        }
    }

    protected void stopBtService() {
        unbindFromService();
        getActivity().getApplicationContext()
                .stopService(new Intent(getActivity().getApplicationContext(), SignalReaderService.class));
    }

    /**
     * Handles starting of the service if all necessary conditions are met.
     */
    protected void startBtService() {
        if (BluetoothAdapter.getDefaultAdapter().isEnabled()) {
            Intent startIntent = new Intent(getActivity().getApplicationContext(), SignalReaderService.class);
            getActivity().getApplicationContext().startService(startIntent);
        } else {
            Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(intent, REQUEST_CODE_ENABLE_BT);
            updateBtServiceUI();
        }
    }

    /**
     * Updates the signal strength listed on the BluetoothFragment.
     *
     * @param newSignalStrength The updated signal strength value.
     */
    protected void updateSignalStrength(int newSignalStrength) {
        if (signalStrengthView != null && newSignalStrength != Integer.MIN_VALUE) {
            signalStrengthView.setText(String.valueOf(newSignalStrength));
        } else if (newSignalStrength == Integer.MIN_VALUE) {
            signalStrengthView.setText(getResources().getString(R.string.loading));
        }
    }

    private void updateBtServiceUI() {
        //Update the switch
        serviceToggle.silentlySetChecked(serviceBound);
    }

    /**
     * Returns the millisecond value corresponding to the value selected in the refresh interval spinner.
     *
     * @param position The position of the selected item in the spinner.
     * @return The {@link java.lang.Long} millisecond value the passed position represents.
     */
    public static long interpretRefreshSpinner(int position) {
        return new long[] { 1000, 2000, 3000 }[position];
    }

    @Override
    public void onItemSelected(AdapterView<?> adapterView, View view, int position, long rowId) {
        SharedPreferences.Editor prefsEditor = userPrefs.edit();
        switch (adapterView.getId()) {
        case R.id.bt_device_chooser:
            //Feed the selected device's address to BluetoothManager
            Set<BluetoothDevice> devicesSet = BluetoothManager.getAllBtDevices();
            BluetoothDevice chosenDevice = (BluetoothDevice) devicesSet.toArray()[position];
            BluetoothManager.setSelectedDevice(chosenDevice);

            //Update saved preference
            prefsEditor.putString(BluetoothFragment.PREF_BT_DEVICE_ADDRESS, chosenDevice.getAddress());
            break;

        case R.id.bt_lock_distances:
            //Update saved preference
            prefsEditor.putInt(PREF_LOCK_DISTANCE, lockDistance.getSelectedItemPosition());
            break;

        case R.id.bt_refresh_interval:
            //Save the spinner value into our instance variable here
            refreshInterval = interpretRefreshSpinner(refreshIntervalSpinner.getSelectedItemPosition());

            //Update saved preference
            prefsEditor.putInt(PREF_REFRESH_INTERVAL, refreshIntervalSpinner.getSelectedItemPosition());
            break;
        }
        prefsEditor.apply();

        //Call startService again (if the service is running) in order to update its settings based on new prefs.
        if (serviceBound) {
            startBtService();
        }
    }

    @Override
    public void onNothingSelected(AdapterView<?> adapterView) {
        //Nothing
    }

    @Override
    public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
        SharedPreferences.Editor prefsEditor = userPrefs.edit();
        switch (compoundButton.getId()) {
        case R.id.button_bt_service_start_stop:
            toggleBtService(isChecked);
            break;
        case R.id.bt_lock_instantly:
            //Save preference
            prefsEditor.putBoolean(PREF_INSTANT_LOCK, isChecked);
            break;
        }
        prefsEditor.apply();
    }

    /**
     * Used to receive updates about Bluetooth state (enabled/disabled/etc).
     */
    private class BluetoothStateReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.getAction().equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
                final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
                switch (state) {
                case BluetoothAdapter.STATE_ON:
                    Log.d(MainActivity.DEBUG_TAG, "Received broadcast: Bluetooth enabled");
                    enableUiElement(deviceChooser);
                    break;

                case BluetoothAdapter.STATE_TURNING_OFF:
                    Log.d(MainActivity.DEBUG_TAG, "Received broadcast: Bluetooth disabled");
                    disableUiElement(deviceChooser);
                    stopBtService();
                    break;
                }
            }
        }
    }

    /**
     * Used in the main bluetooth fragment to receive signal strength from SignalReaderService.
     */
    public class LocalBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            switch (intent.getAction()) {
            case SignalReaderService.ACTION_SIGNAL_STRENGTH_UPDATE:
                //If the broadcast was fired with this action, we know to update the UI
                Log.d(MainActivity.DEBUG_TAG, "Local BT signal broadcast received.");

                //Update signal strength
                int newSignalStrength = intent.getIntExtra("message", Integer.MIN_VALUE);
                updateSignalStrength(newSignalStrength);
                break;

            case SignalReaderService.ACTION_UNBIND_SERVICE:
                //We need to unbind from the service so it can shut down
                Log.d(MainActivity.DEBUG_TAG, "Unbind broadcast received.");
                unbindFromService();
                break;
            }
        }
    }

    /**
     * Used for alerting the user that they'll be redirected to Settings to enable device admin.
     */
    public static class AdminDialogFragment extends DialogFragment {
        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
            builder.setTitle(getResources().getString(R.string.device_admin_dialog_title))
                    .setMessage(getResources().getString(R.string.device_admin_dialog_text)).setPositiveButton(
                            getResources().getString(R.string.ok), new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialogInterface, int i) {
                                    Intent activateAdminIntent = new Intent(
                                            DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
                                    activateAdminIntent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
                                            new ComponentName(getActivity().getApplicationContext(),
                                                    DeviceLockManager.class));
                                    activateAdminIntent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION,
                                            getResources().getString(R.string.device_admin_description));
                                    startActivityForResult(activateAdminIntent, REQUEST_CODE_ENABLE_ADMIN);
                                }
                            })
                    .setNegativeButton(getResources().getString(R.string.nope),
                            new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialogInterface, int i) {
                                    getActivity().finish();
                                }
                            });
            return builder.create();
        }
    }
}