/* * Copyright 2018 Google LLC All Rights Reserved. * * 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.ginkage.wearmouse.ui.devices; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; import android.os.StrictMode; import android.preference.Preference; import android.preference.PreferenceFragment; import android.support.wearable.preference.WearablePreferenceActivity; import android.util.Log; import androidx.annotation.MainThread; import com.ginkage.wearmouse.R; import com.ginkage.wearmouse.bluetooth.HidDataSender; import com.ginkage.wearmouse.bluetooth.HidDeviceProfile; import com.ginkage.wearmouse.ui.input.ModeSelectFragment; import java.util.ArrayList; import java.util.List; /** Paired Bluetooth devices list. */ public class PairedDevicesFragment extends PreferenceFragment { private static final String TAG = "BluetoothSettings"; private static final int PREFERENCE_ORDER_NORMAL = 100; private BluetoothAdapter bluetoothAdapter; private HidDeviceProfile hidDeviceProfile; private HidDataSender hidDataSender; private final List<Preference> bondedDevices = new ArrayList<>(); private boolean scanReceiverRegistered; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); try { addPreferencesFromResource(R.xml.prefs_paired_devices); } finally { StrictMode.setThreadPolicy(oldPolicy); } Context context = getContext(); bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); hidDataSender = HidDataSender.getInstance(); hidDeviceProfile = hidDataSender.register(context, profileListener); context.registerReceiver( bluetoothStateReceiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); } @Override public void onStart() { super.onStart(); if (!bluetoothAdapter.isEnabled()) { bluetoothAdapter.enable(); } } @Override public void onResume() { super.onResume(); updateBluetoothStateAndDevices(); } @Override public void onPause() { unregisterScanReceiver(); super.onPause(); } @Override public void onDestroy() { Context context = getContext(); context.unregisterReceiver(bluetoothStateReceiver); hidDataSender.unregister(context, profileListener); context.stopService(new Intent(context, NotificationService.class)); super.onDestroy(); } protected void updateBluetoothStateAndDevices() { switch (bluetoothAdapter.getState()) { case BluetoothAdapter.STATE_OFF: unregisterScanReceiver(); clearBondedDevices(); break; case BluetoothAdapter.STATE_TURNING_ON: case BluetoothAdapter.STATE_TURNING_OFF: clearBondedDevices(); startActivity(new Intent(getActivity(), BluetoothStateActivity.class)); break; case BluetoothAdapter.STATE_ON: registerScanReceiver(); updateBondedDevices(); break; default: // fall out } } protected void updateBondedDevices() { for (BluetoothDevice device : bluetoothAdapter.getBondedDevices()) { updatePreferenceBondState(device); } } /** Examine the bond state of the device and update preference if necessary. */ private void updatePreferenceBondState(final BluetoothDevice device) { final BluetoothDevicePreference pref = findOrAllocateDevicePreference(device); pref.updateBondState(); pref.updateProfileConnectionState(); switch (device.getBondState()) { case BluetoothDevice.BOND_BONDED: pref.setEnabled(true); pref.setOrder(PREFERENCE_ORDER_NORMAL); bondedDevices.add(pref); getPreferenceScreen().addPreference(pref); break; case BluetoothDevice.BOND_NONE: pref.setEnabled(false); bondedDevices.remove(pref); getPreferenceScreen().removePreference(pref); break; case BluetoothDevice.BOND_BONDING: pref.setEnabled(false); break; default: // fall out } } protected void clearBondedDevices() { for (Preference p : bondedDevices) { getPreferenceScreen().removePreference(p); } bondedDevices.clear(); } private void registerScanReceiver() { final IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(BluetoothDevice.ACTION_NAME_CHANGED); intentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); getContext().registerReceiver(bluetoothScanReceiver, intentFilter); scanReceiverRegistered = true; BluetoothUtils.setScanMode( bluetoothAdapter, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, 0); } private void unregisterScanReceiver() { if (scanReceiverRegistered) { getContext().unregisterReceiver(bluetoothScanReceiver); scanReceiverRegistered = false; BluetoothUtils.setScanMode(bluetoothAdapter, BluetoothAdapter.SCAN_MODE_CONNECTABLE, 0); } } private final HidDataSender.ProfileListener profileListener = new HidDataSender.ProfileListener() { @Override @MainThread public void onServiceStateChanged(BluetoothProfile proxy) { if (proxy != null) { for (final BluetoothDevice device : bluetoothAdapter.getBondedDevices()) { final BluetoothDevicePreference pref = findDevicePreference(device); if (pref != null) { pref.updateProfileConnectionState(); } } } } @Override @MainThread public void onConnectionStateChanged(BluetoothDevice device, int state) { updatePreferenceBondState(device); Context context = getContext(); if (state != BluetoothProfile.STATE_DISCONNECTED) { Intent intent = NotificationService.buildIntent(device.getName(), state); intent.setClass(context, NotificationService.class); context.startService(intent); } if (state == BluetoothProfile.STATE_CONNECTED) { ((WearablePreferenceActivity) getActivity()) .startPreferenceFragment(new ModeSelectFragment(), true); } } @Override @MainThread public void onAppStatusChanged(boolean registered) { if (!registered) { getActivity().finish(); } } }; /** Handles bluetooth scan responses and other indicators. */ private final BroadcastReceiver bluetoothScanReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (getContext() == null) { Log.w(TAG, "BluetoothScanReceiver got intent with no context"); return; } final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); final String action = intent.getAction(); switch (action == null ? "" : action) { case BluetoothDevice.ACTION_BOND_STATE_CHANGED: updatePreferenceBondState(device); break; case BluetoothDevice.ACTION_NAME_CHANGED: BluetoothDevicePreference pref = findDevicePreference(device); if (pref != null) { pref.updateName(); } break; default: // fall out } } }; /** Receiver to listen for changes in the bluetooth adapter state. */ private final BroadcastReceiver bluetoothStateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (getContext() == null) { Log.w(TAG, "BluetoothStateReceiver got intent with no context"); return; } if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) { updateBluetoothStateAndDevices(); } } }; /** * Looks for a preference in the preference group. * * <p>Returns null if no preference found. */ private BluetoothDevicePreference findDevicePreference(final BluetoothDevice device) { return (BluetoothDevicePreference) findPreference(device.getAddress()); } /** * Looks for a preference in the preference group. * * <p>Allocates a new preference if none found. */ private BluetoothDevicePreference findOrAllocateDevicePreference(final BluetoothDevice device) { BluetoothDevicePreference pref = findDevicePreference(device); if (pref == null) { pref = new BluetoothDevicePreference(getContext(), device, hidDeviceProfile); } return pref; } }