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