1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.accessibility;
18 
19 import android.app.settings.SettingsEnums;
20 import android.bluetooth.BluetoothAdapter;
21 import android.bluetooth.BluetoothDevice;
22 import android.bluetooth.BluetoothHearingAid;
23 import android.bluetooth.BluetoothProfile;
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.os.Bundle;
29 import android.text.TextUtils;
30 import android.util.Log;
31 
32 import androidx.annotation.VisibleForTesting;
33 import androidx.fragment.app.FragmentManager;
34 import androidx.lifecycle.Lifecycle.Event;
35 import androidx.lifecycle.LifecycleObserver;
36 import androidx.lifecycle.OnLifecycleEvent;
37 import androidx.preference.Preference;
38 import androidx.preference.PreferenceScreen;
39 
40 import com.android.settings.R;
41 import com.android.settings.bluetooth.BluetoothDeviceDetailsFragment;
42 import com.android.settings.core.BasePreferenceController;
43 import com.android.settings.core.SubSettingLauncher;
44 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
45 import com.android.settingslib.bluetooth.LocalBluetoothManager;
46 
47 import java.util.Iterator;
48 import java.util.List;
49 import java.util.concurrent.ExecutionException;
50 import java.util.concurrent.FutureTask;
51 
52 /**
53  * Controller that shows and updates the bluetooth device name
54  */
55 public class AccessibilityHearingAidPreferenceController extends BasePreferenceController
56         implements LifecycleObserver {
57     private static final String TAG = "AccessibilityHearingAidPreferenceController";
58     private Preference mHearingAidPreference;
59 
60     private final BroadcastReceiver mHearingAidChangedReceiver = new BroadcastReceiver() {
61         @Override
62         public void onReceive(Context context, Intent intent) {
63             if (BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
64                 final int state = intent.getIntExtra(BluetoothHearingAid.EXTRA_STATE,
65                         BluetoothHearingAid.STATE_DISCONNECTED);
66                 if (state == BluetoothHearingAid.STATE_CONNECTED) {
67                     updateState(mHearingAidPreference);
68                 } else {
69                     mHearingAidPreference
70                             .setSummary(R.string.accessibility_hearingaid_not_connected_summary);
71                 }
72             } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) {
73                 final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
74                         BluetoothAdapter.ERROR);
75                 if (state != BluetoothAdapter.STATE_ON) {
76                     mHearingAidPreference
77                             .setSummary(R.string.accessibility_hearingaid_not_connected_summary);
78                 }
79             }
80         }
81     };
82 
83     private final LocalBluetoothManager mLocalBluetoothManager;
84     private final BluetoothAdapter mBluetoothAdapter;
85     //cache value of supporting hearing aid or not
86     private boolean mHearingAidProfileSupported;
87     private FragmentManager mFragmentManager;
88 
AccessibilityHearingAidPreferenceController(Context context, String preferenceKey)89     public AccessibilityHearingAidPreferenceController(Context context, String preferenceKey) {
90         super(context, preferenceKey);
91         mLocalBluetoothManager = getLocalBluetoothManager();
92         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
93         mHearingAidProfileSupported = isHearingAidProfileSupported();
94     }
95 
96     @Override
displayPreference(PreferenceScreen screen)97     public void displayPreference(PreferenceScreen screen) {
98         super.displayPreference(screen);
99         mHearingAidPreference = screen.findPreference(getPreferenceKey());
100     }
101 
102     @Override
getAvailabilityStatus()103     public int getAvailabilityStatus() {
104         return mHearingAidProfileSupported ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
105     }
106 
107     @OnLifecycleEvent(Event.ON_RESUME)
onResume()108     public void onResume() {
109         if (mHearingAidProfileSupported) {
110             IntentFilter filter = new IntentFilter();
111             filter.addAction(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
112             filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
113             mContext.registerReceiver(mHearingAidChangedReceiver, filter);
114         }
115     }
116 
117     @OnLifecycleEvent(Event.ON_PAUSE)
onPause()118     public void onPause() {
119         if (mHearingAidProfileSupported) {
120             mContext.unregisterReceiver(mHearingAidChangedReceiver);
121         }
122     }
123 
124     @Override
handlePreferenceTreeClick(Preference preference)125     public boolean handlePreferenceTreeClick(Preference preference) {
126         if (TextUtils.equals(preference.getKey(), getPreferenceKey())){
127             final CachedBluetoothDevice device = getConnectedHearingAidDevice();
128             if (device == null) {
129                 launchHearingAidInstructionDialog();
130             } else {
131                 launchBluetoothDeviceDetailSetting(device);
132             }
133             return true;
134         }
135         return false;
136     }
137 
138     @Override
getSummary()139     public CharSequence getSummary() {
140         final CachedBluetoothDevice device = getConnectedHearingAidDevice();
141         if (device == null) {
142             return mContext.getText(R.string.accessibility_hearingaid_not_connected_summary);
143         }
144         return device.getName();
145     }
146 
setFragmentManager(FragmentManager fragmentManager)147     public void setFragmentManager(FragmentManager fragmentManager) {
148         mFragmentManager = fragmentManager;
149     }
150 
151     @VisibleForTesting
getConnectedHearingAidDevice()152     CachedBluetoothDevice getConnectedHearingAidDevice() {
153         if (!mHearingAidProfileSupported) {
154             return null;
155         }
156         if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
157             return null;
158         }
159         final List<BluetoothDevice> deviceList = mLocalBluetoothManager.getProfileManager()
160                 .getHearingAidProfile().getConnectedDevices();
161         final Iterator it = deviceList.iterator();
162         while (it.hasNext()) {
163             BluetoothDevice obj = (BluetoothDevice)it.next();
164             if (!mLocalBluetoothManager.getCachedDeviceManager().isSubDevice(obj)) {
165                 return mLocalBluetoothManager.getCachedDeviceManager().findDevice(obj);
166             }
167         }
168         return null;
169     }
170 
isHearingAidProfileSupported()171     private boolean isHearingAidProfileSupported() {
172         if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
173             return false;
174         }
175         final List<Integer> supportedList = mBluetoothAdapter.getSupportedProfiles();
176         if (supportedList.contains(BluetoothProfile.HEARING_AID)) {
177             return true;
178         }
179         return false;
180     }
181 
getLocalBluetoothManager()182     private LocalBluetoothManager getLocalBluetoothManager() {
183         final FutureTask<LocalBluetoothManager> localBtManagerFutureTask = new FutureTask<>(
184                 // Avoid StrictMode ThreadPolicy violation
185                 () -> com.android.settings.bluetooth.Utils.getLocalBtManager(mContext));
186         try {
187             localBtManagerFutureTask.run();
188             return localBtManagerFutureTask.get();
189         } catch (InterruptedException | ExecutionException e) {
190             Log.w(TAG, "Error getting LocalBluetoothManager.", e);
191             return null;
192         }
193     }
194 
195     @VisibleForTesting(otherwise = VisibleForTesting.NONE)
setPreference(Preference preference)196     void setPreference(Preference preference) {
197         mHearingAidPreference = preference;
198     }
199 
200     @VisibleForTesting
launchBluetoothDeviceDetailSetting(final CachedBluetoothDevice device)201     void launchBluetoothDeviceDetailSetting(final CachedBluetoothDevice device) {
202         if (device == null) {
203             return;
204         }
205         final Bundle args = new Bundle();
206         args.putString(BluetoothDeviceDetailsFragment.KEY_DEVICE_ADDRESS,
207                 device.getDevice().getAddress());
208 
209         new SubSettingLauncher(mContext)
210                 .setDestination(BluetoothDeviceDetailsFragment.class.getName())
211                 .setArguments(args)
212                 .setTitleRes(R.string.device_details_title)
213                 .setSourceMetricsCategory(SettingsEnums.ACCESSIBILITY)
214                 .launch();
215     }
216 
217     @VisibleForTesting
launchHearingAidInstructionDialog()218     void launchHearingAidInstructionDialog() {
219         HearingAidDialogFragment fragment = HearingAidDialogFragment.newInstance();
220         fragment.show(mFragmentManager, HearingAidDialogFragment.class.toString());
221     }
222 }