• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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