1 /* 2 * Copyright (C) 2019 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.car.dialer.ui; 18 19 import android.annotation.IntDef; 20 import android.app.Application; 21 import android.bluetooth.BluetoothAdapter; 22 import android.bluetooth.BluetoothDevice; 23 import android.bluetooth.BluetoothProfile; 24 import android.content.Context; 25 26 import androidx.lifecycle.AndroidViewModel; 27 import androidx.lifecycle.LiveData; 28 import androidx.lifecycle.MediatorLiveData; 29 import androidx.lifecycle.MutableLiveData; 30 31 import com.android.car.dialer.R; 32 import com.android.car.dialer.livedata.BluetoothHfpStateLiveData; 33 import com.android.car.dialer.livedata.BluetoothPairListLiveData; 34 import com.android.car.dialer.livedata.BluetoothStateLiveData; 35 import com.android.car.dialer.log.L; 36 import com.android.car.dialer.telecom.UiBluetoothMonitor; 37 38 import java.lang.annotation.Retention; 39 import java.lang.annotation.RetentionPolicy; 40 import java.util.Set; 41 42 /** 43 * View model for {@link TelecomActivity}. 44 */ 45 public class TelecomActivityViewModel extends AndroidViewModel { 46 private static final String TAG = "CD.TelecomActivityViewModel"; 47 /** A constant which indicates that there's no Bluetooth error. */ 48 public static final String NO_BT_ERROR = "NO_ERROR"; 49 50 private final Context mApplicationContext; 51 private final LiveData<String> mErrorStringLiveData; 52 private final MutableLiveData<Integer> mDialerAppStateLiveData; 53 54 /** 55 * App state indicates if bluetooth is connected or it should just show the content fragments. 56 */ 57 @IntDef({DialerAppState.DEFAULT, DialerAppState.BLUETOOTH_ERROR, 58 DialerAppState.EMERGENCY_DIALPAD}) 59 @Retention(RetentionPolicy.SOURCE) 60 public @interface DialerAppState { 61 int DEFAULT = 0; 62 int BLUETOOTH_ERROR = 1; 63 int EMERGENCY_DIALPAD = 2; 64 } 65 TelecomActivityViewModel(Application application)66 public TelecomActivityViewModel(Application application) { 67 super(application); 68 mApplicationContext = application.getApplicationContext(); 69 70 if (BluetoothAdapter.getDefaultAdapter() == null) { 71 MutableLiveData<String> bluetoothUnavailableLiveData = new MutableLiveData<>(); 72 bluetoothUnavailableLiveData.setValue( 73 mApplicationContext.getString(R.string.bluetooth_unavailable)); 74 mErrorStringLiveData = bluetoothUnavailableLiveData; 75 } else { 76 UiBluetoothMonitor uiBluetoothMonitor = UiBluetoothMonitor.get(); 77 mErrorStringLiveData = new ErrorStringLiveData( 78 mApplicationContext, 79 uiBluetoothMonitor.getHfpStateLiveData(), 80 uiBluetoothMonitor.getPairListLiveData(), 81 uiBluetoothMonitor.getBluetoothStateLiveData()); 82 } 83 84 mDialerAppStateLiveData = new DialerAppStateLiveData(mErrorStringLiveData); 85 } 86 getDialerAppState()87 public MutableLiveData<Integer> getDialerAppState() { 88 return mDialerAppStateLiveData; 89 } 90 91 /** 92 * Returns a LiveData which provides the warning string based on Bluetooth states. Returns 93 * {@link #NO_BT_ERROR} if there's no error. 94 */ getErrorMessage()95 public LiveData<String> getErrorMessage() { 96 return mErrorStringLiveData; 97 } 98 99 private static class DialerAppStateLiveData extends MediatorLiveData<Integer> { 100 private final LiveData<String> mErrorStringLiveData; 101 DialerAppStateLiveData(LiveData<String> errorStringLiveData)102 private DialerAppStateLiveData(LiveData<String> errorStringLiveData) { 103 this.mErrorStringLiveData = errorStringLiveData; 104 setValue(DialerAppState.DEFAULT); 105 106 addSource(mErrorStringLiveData, errorMsg -> updateDialerAppState()); 107 } 108 updateDialerAppState()109 private void updateDialerAppState() { 110 L.d(TAG, "updateDialerAppState, error: %s", mErrorStringLiveData.getValue()); 111 112 // If bluetooth is not connected, user can make an emergency call. So show the in 113 // call fragment no matter if bluetooth is connected or not. 114 // Bluetooth error 115 if (!NO_BT_ERROR.equals(mErrorStringLiveData.getValue())) { 116 // Currently bluetooth is not connected, stay on the emergency dial pad page. 117 if (getValue() == DialerAppState.EMERGENCY_DIALPAD) { 118 return; 119 } 120 setValue(DialerAppState.BLUETOOTH_ERROR); 121 return; 122 } 123 124 // Bluetooth connected. 125 setValue(DialerAppState.DEFAULT); 126 } 127 128 @Override setValue(@ialerAppState Integer newValue)129 public void setValue(@DialerAppState Integer newValue) { 130 // Only set value and notify observers when the value changes. 131 if (getValue() != newValue) { 132 super.setValue(newValue); 133 } 134 } 135 } 136 137 private static class ErrorStringLiveData extends MediatorLiveData<String> { 138 private LiveData<Integer> mHfpStateLiveData; 139 private LiveData<Set<BluetoothDevice>> mPairedListLiveData; 140 private LiveData<Integer> mBluetoothStateLiveData; 141 142 private Context mContext; 143 ErrorStringLiveData(Context context, BluetoothHfpStateLiveData hfpStateLiveData, BluetoothPairListLiveData pairListLiveData, BluetoothStateLiveData bluetoothStateLiveData)144 ErrorStringLiveData(Context context, 145 BluetoothHfpStateLiveData hfpStateLiveData, 146 BluetoothPairListLiveData pairListLiveData, 147 BluetoothStateLiveData bluetoothStateLiveData) { 148 mContext = context; 149 mHfpStateLiveData = hfpStateLiveData; 150 mPairedListLiveData = pairListLiveData; 151 mBluetoothStateLiveData = bluetoothStateLiveData; 152 setValue(NO_BT_ERROR); 153 154 addSource(hfpStateLiveData, this::onHfpStateChanged); 155 addSource(pairListLiveData, this::onPairListChanged); 156 addSource(bluetoothStateLiveData, this::onBluetoothStateChanged); 157 } 158 onHfpStateChanged(Integer state)159 private void onHfpStateChanged(Integer state) { 160 update(); 161 } 162 onPairListChanged(Set<BluetoothDevice> pairedDevices)163 private void onPairListChanged(Set<BluetoothDevice> pairedDevices) { 164 update(); 165 } 166 onBluetoothStateChanged(Integer state)167 private void onBluetoothStateChanged(Integer state) { 168 update(); 169 } 170 update()171 private void update() { 172 boolean isBluetoothEnabled = isBluetoothEnabled(); 173 boolean hasPairedDevices = hasPairedDevices(); 174 boolean isHfpConnected = isHfpConnected(); 175 L.d(TAG, "Update error string." 176 + " isBluetoothEnabled: %s" 177 + " hasPairedDevices: %s" 178 + " isHfpConnected: %s", 179 isBluetoothEnabled, 180 hasPairedDevices, 181 isHfpConnected); 182 if (!isBluetoothEnabled) { 183 setValue(mContext.getString(R.string.bluetooth_disabled)); 184 } else if (!hasPairedDevices) { 185 setValue(mContext.getString(R.string.bluetooth_unpaired)); 186 } else if (!isHfpConnected) { 187 setValue(mContext.getString(R.string.no_hfp)); 188 } else { 189 if (!NO_BT_ERROR.equals(getValue())) { 190 setValue(NO_BT_ERROR); 191 } 192 } 193 } 194 isHfpConnected()195 private boolean isHfpConnected() { 196 Integer hfpState = mHfpStateLiveData.getValue(); 197 return hfpState == null || hfpState == BluetoothProfile.STATE_CONNECTED; 198 } 199 isBluetoothEnabled()200 private boolean isBluetoothEnabled() { 201 Integer bluetoothState = mBluetoothStateLiveData.getValue(); 202 return bluetoothState == null 203 || bluetoothState != BluetoothStateLiveData.BluetoothState.DISABLED; 204 } 205 hasPairedDevices()206 private boolean hasPairedDevices() { 207 Set<BluetoothDevice> pairedDevices = mPairedListLiveData.getValue(); 208 return pairedDevices == null || !pairedDevices.isEmpty(); 209 } 210 } 211 } 212