1 /* 2 * Copyright 2020 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.server.bluetooth; 18 19 import static com.android.server.bluetooth.BluetoothAirplaneModeListener.BLUETOOTH_APM_STATE; 20 import static com.android.server.bluetooth.BluetoothAirplaneModeListener.BT_DEFAULT_APM_STATE; 21 22 import android.annotation.RequiresPermission; 23 import android.app.ActivityManager; 24 import android.bluetooth.BluetoothA2dp; 25 import android.bluetooth.BluetoothAdapter; 26 import android.bluetooth.BluetoothHearingAid; 27 import android.bluetooth.BluetoothLeAudio; 28 import android.bluetooth.BluetoothProfile; 29 import android.bluetooth.BluetoothProfile.ServiceListener; 30 import android.content.Context; 31 import android.content.pm.PackageManager; 32 import android.content.res.Resources; 33 import android.os.Process; 34 import android.os.UserHandle; 35 import android.provider.Settings; 36 import android.util.Log; 37 import android.widget.Toast; 38 39 import com.android.internal.annotations.VisibleForTesting; 40 41 /** 42 * Helper class that handles callout and callback methods without 43 * complex logic. 44 */ 45 public class BluetoothModeChangeHelper { 46 private static final String TAG = "BluetoothModeChangeHelper"; 47 48 private volatile BluetoothA2dp mA2dp; 49 private volatile BluetoothHearingAid mHearingAid; 50 private volatile BluetoothLeAudio mLeAudio; 51 private final BluetoothAdapter mAdapter; 52 private final Context mContext; 53 54 private String mBluetoothPackageName; 55 BluetoothModeChangeHelper(Context context)56 BluetoothModeChangeHelper(Context context) { 57 mAdapter = BluetoothAdapter.getDefaultAdapter(); 58 mContext = context; 59 60 mAdapter.getProfileProxy(mContext, mProfileServiceListener, BluetoothProfile.A2DP); 61 mAdapter.getProfileProxy(mContext, mProfileServiceListener, 62 BluetoothProfile.HEARING_AID); 63 mAdapter.getProfileProxy(mContext, mProfileServiceListener, BluetoothProfile.LE_AUDIO); 64 } 65 66 private final ServiceListener mProfileServiceListener = new ServiceListener() { 67 @Override 68 public void onServiceConnected(int profile, BluetoothProfile proxy) { 69 // Setup Bluetooth profile proxies 70 switch (profile) { 71 case BluetoothProfile.A2DP: 72 mA2dp = (BluetoothA2dp) proxy; 73 break; 74 case BluetoothProfile.HEARING_AID: 75 mHearingAid = (BluetoothHearingAid) proxy; 76 break; 77 case BluetoothProfile.LE_AUDIO: 78 mLeAudio = (BluetoothLeAudio) proxy; 79 break; 80 default: 81 break; 82 } 83 } 84 85 @Override 86 public void onServiceDisconnected(int profile) { 87 // Clear Bluetooth profile proxies 88 switch (profile) { 89 case BluetoothProfile.A2DP: 90 mA2dp = null; 91 break; 92 case BluetoothProfile.HEARING_AID: 93 mHearingAid = null; 94 break; 95 case BluetoothProfile.LE_AUDIO: 96 mLeAudio = null; 97 break; 98 default: 99 break; 100 } 101 } 102 }; 103 104 @VisibleForTesting isMediaProfileConnected()105 public boolean isMediaProfileConnected() { 106 return isA2dpConnected() || isHearingAidConnected() || isLeAudioConnected(); 107 } 108 109 @VisibleForTesting isBluetoothOn()110 public boolean isBluetoothOn() { 111 final BluetoothAdapter adapter = mAdapter; 112 if (adapter == null) { 113 return false; 114 } 115 return adapter.isLeEnabled(); 116 } 117 118 @VisibleForTesting isAirplaneModeOn()119 public boolean isAirplaneModeOn() { 120 return Settings.Global.getInt(mContext.getContentResolver(), 121 Settings.Global.AIRPLANE_MODE_ON, 0) == 1; 122 } 123 124 @VisibleForTesting 125 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) onAirplaneModeChanged(BluetoothManagerService managerService)126 public void onAirplaneModeChanged(BluetoothManagerService managerService) { 127 managerService.onAirplaneModeChanged(); 128 } 129 130 @VisibleForTesting getSettingsInt(String name)131 public int getSettingsInt(String name) { 132 return Settings.Global.getInt(mContext.getContentResolver(), 133 name, 0); 134 } 135 136 @VisibleForTesting setSettingsInt(String name, int value)137 public void setSettingsInt(String name, int value) { 138 Settings.Global.putInt(mContext.getContentResolver(), 139 name, value); 140 } 141 142 /** 143 * Helper method to get Settings Secure Int value 144 */ getSettingsSecureInt(String name, int def)145 public int getSettingsSecureInt(String name, int def) { 146 Context userContext = mContext.createContextAsUser( 147 UserHandle.of(ActivityManager.getCurrentUser()), 0); 148 return Settings.Secure.getInt(userContext.getContentResolver(), name, def); 149 } 150 151 /** 152 * Helper method to set Settings Secure Int value 153 */ setSettingsSecureInt(String name, int value)154 public void setSettingsSecureInt(String name, int value) { 155 Context userContext = mContext.createContextAsUser( 156 UserHandle.of(ActivityManager.getCurrentUser()), 0); 157 Settings.Secure.putInt(userContext.getContentResolver(), name, value); 158 } 159 160 @VisibleForTesting showToastMessage()161 public void showToastMessage() { 162 Resources r = mContext.getResources(); 163 final CharSequence text = r.getString(Resources.getSystem().getIdentifier( 164 "bluetooth_airplane_mode_toast", "string", "android")); 165 Toast.makeText(mContext, text, Toast.LENGTH_LONG).show(); 166 } 167 isA2dpConnected()168 private boolean isA2dpConnected() { 169 final BluetoothA2dp a2dp = mA2dp; 170 if (a2dp == null) { 171 return false; 172 } 173 return a2dp.getConnectedDevices().size() > 0; 174 } 175 isHearingAidConnected()176 private boolean isHearingAidConnected() { 177 final BluetoothHearingAid hearingAid = mHearingAid; 178 if (hearingAid == null) { 179 return false; 180 } 181 return hearingAid.getConnectedDevices().size() > 0; 182 } 183 isLeAudioConnected()184 private boolean isLeAudioConnected() { 185 final BluetoothLeAudio leAudio = mLeAudio; 186 if (leAudio == null) { 187 return false; 188 } 189 return leAudio.getConnectedDevices().size() > 0; 190 } 191 192 /** 193 * Helper method to check whether BT should be enabled on APM 194 */ isBluetoothOnAPM()195 public boolean isBluetoothOnAPM() { 196 Context userContext = mContext.createContextAsUser( 197 UserHandle.of(ActivityManager.getCurrentUser()), 0); 198 int defaultBtApmState = getSettingsInt(BT_DEFAULT_APM_STATE); 199 return Settings.Secure.getInt(userContext.getContentResolver(), 200 BLUETOOTH_APM_STATE, defaultBtApmState) == 1; 201 } 202 203 /** 204 * Helper method to retrieve BT package name with APM resources 205 */ getBluetoothPackageName()206 public String getBluetoothPackageName() { 207 if (mBluetoothPackageName != null) { 208 return mBluetoothPackageName; 209 } 210 var allPackages = mContext.getPackageManager().getPackagesForUid(Process.BLUETOOTH_UID); 211 for (String candidatePackage : allPackages) { 212 Resources resources; 213 try { 214 resources = mContext.getPackageManager() 215 .getResourcesForApplication(candidatePackage); 216 } catch (PackageManager.NameNotFoundException e) { 217 // ignore, try next package 218 Log.e(TAG, "Could not find package " + candidatePackage); 219 continue; 220 } catch (Exception e) { 221 Log.e(TAG, "Error while loading package" + e); 222 continue; 223 } 224 if (resources.getIdentifier("bluetooth_and_wifi_stays_on_title", 225 "string", candidatePackage) == 0) { 226 continue; 227 } 228 mBluetoothPackageName = candidatePackage; 229 } 230 return mBluetoothPackageName; 231 } 232 } 233