1 /*
2  * Copyright (C) 2016 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.dialer.app.voicemail.error;
18 
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.database.Cursor;
22 import android.net.Uri;
23 import android.os.Build.VERSION;
24 import android.os.Build.VERSION_CODES;
25 import android.provider.Settings;
26 import android.provider.Settings.Global;
27 import android.provider.VoicemailContract.Status;
28 import android.support.annotation.Nullable;
29 import android.support.v4.os.BuildCompat;
30 import android.telecom.PhoneAccountHandle;
31 import android.telephony.ServiceState;
32 import android.telephony.TelephonyManager;
33 import com.android.dialer.common.LogUtil;
34 import com.android.dialer.database.VoicemailStatusQuery;
35 
36 /** Structured data from {@link android.provider.VoicemailContract.Status} */
37 public class VoicemailStatus {
38 
39   public final String sourcePackage;
40   public final String type;
41 
42   public final String phoneAccountComponentName;
43   public final String phoneAccountId;
44 
45   @Nullable public final Uri settingsUri;
46   @Nullable public final Uri voicemailAccessUri;
47 
48   public final int configurationState;
49   public final int dataChannelState;
50   public final int notificationChannelState;
51 
52   public final int quotaOccupied;
53   public final int quotaTotal;
54 
55   // System status
56 
57   public final boolean isAirplaneMode;
58 
59   /** Wraps the row currently pointed by <code>statusCursor</code> */
VoicemailStatus(Context context, Cursor statusCursor)60   public VoicemailStatus(Context context, Cursor statusCursor) {
61     sourcePackage = getString(statusCursor, VoicemailStatusQuery.SOURCE_PACKAGE_INDEX, "");
62 
63     settingsUri = getUri(statusCursor, VoicemailStatusQuery.SETTINGS_URI_INDEX);
64     voicemailAccessUri = getUri(statusCursor, VoicemailStatusQuery.VOICEMAIL_ACCESS_URI_INDEX);
65 
66     if (VERSION.SDK_INT >= VERSION_CODES.N_MR1) {
67       type =
68           getString(
69               statusCursor, VoicemailStatusQuery.SOURCE_TYPE_INDEX, TelephonyManager.VVM_TYPE_OMTP);
70       phoneAccountComponentName =
71           getString(statusCursor, VoicemailStatusQuery.PHONE_ACCOUNT_COMPONENT_NAME, "");
72       phoneAccountId = getString(statusCursor, VoicemailStatusQuery.PHONE_ACCOUNT_ID, "");
73     } else {
74       type = TelephonyManager.VVM_TYPE_OMTP;
75       phoneAccountComponentName = "";
76       phoneAccountId = "";
77     }
78 
79     configurationState =
80         getInt(
81             statusCursor,
82             VoicemailStatusQuery.CONFIGURATION_STATE_INDEX,
83             Status.CONFIGURATION_STATE_NOT_CONFIGURED);
84     dataChannelState =
85         getInt(
86             statusCursor,
87             VoicemailStatusQuery.DATA_CHANNEL_STATE_INDEX,
88             Status.DATA_CHANNEL_STATE_NO_CONNECTION);
89 
90     /* Before O, the NOTIFICATION_CHANNEL_STATE in the voicemail status table for the system
91      * visual voicemail client always correspond to the service state (cellular signal availability)
92      * Tracking the state in the background is redundant because it will not be visible to the
93      * user. It is much simpler to poll the status on the UI side. The result is injected back to
94      * the status query result so the handling will be consistent with other voicemail clients.
95      */
96     if (BuildCompat.isAtLeastO() && sourcePackage.equals(context.getPackageName())) {
97       notificationChannelState =
98           getNotificationChannelStateFormTelephony(context, getPhoneAccountHandle());
99     } else {
100       notificationChannelState =
101           getInt(
102               statusCursor,
103               VoicemailStatusQuery.NOTIFICATION_CHANNEL_STATE_INDEX,
104               Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION);
105     }
106     isAirplaneMode =
107         Settings.System.getInt(context.getContentResolver(), Global.AIRPLANE_MODE_ON, 0) != 0;
108 
109     if (VERSION.SDK_INT >= VERSION_CODES.N) {
110       quotaOccupied =
111           getInt(statusCursor, VoicemailStatusQuery.QUOTA_OCCUPIED_INDEX, Status.QUOTA_UNAVAILABLE);
112       quotaTotal =
113           getInt(statusCursor, VoicemailStatusQuery.QUOTA_TOTAL_INDEX, Status.QUOTA_UNAVAILABLE);
114     } else {
115       quotaOccupied = Status.QUOTA_UNAVAILABLE;
116       quotaTotal = Status.QUOTA_UNAVAILABLE;
117     }
118   }
119 
getNotificationChannelStateFormTelephony( Context context, PhoneAccountHandle phoneAccountHandle)120   private static int getNotificationChannelStateFormTelephony(
121       Context context, PhoneAccountHandle phoneAccountHandle) {
122     TelephonyManager telephonyManager =
123         context
124             .getSystemService(TelephonyManager.class)
125             .createForPhoneAccountHandle(phoneAccountHandle);
126     if (telephonyManager == null) {
127       LogUtil.e("VoicemailStatus.constructor", "invalid PhoneAccountHandle");
128       return Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION;
129     } else {
130       int state = telephonyManager.getServiceState().getState();
131       if (state == ServiceState.STATE_IN_SERVICE) {
132         return Status.NOTIFICATION_CHANNEL_STATE_OK;
133       } else {
134         return Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION;
135       }
136     }
137   }
138 
VoicemailStatus(Builder builder)139   private VoicemailStatus(Builder builder) {
140     sourcePackage = builder.sourcePackage;
141     phoneAccountComponentName = builder.phoneAccountComponentName;
142     phoneAccountId = builder.phoneAccountId;
143     type = builder.type;
144     settingsUri = builder.settingsUri;
145     voicemailAccessUri = builder.voicemailAccessUri;
146     configurationState = builder.configurationState;
147     dataChannelState = builder.dataChannelState;
148     notificationChannelState = builder.notificationChannelState;
149     quotaOccupied = builder.quotaOccupied;
150     quotaTotal = builder.quotaTotal;
151     isAirplaneMode = builder.isAirplaneMode;
152   }
153 
154   static class Builder {
155 
156     private String sourcePackage = "";
157     private String type = TelephonyManager.VVM_TYPE_OMTP;
158     private String phoneAccountComponentName = "";
159     private String phoneAccountId = "";
160 
161     @Nullable private Uri settingsUri;
162     @Nullable private Uri voicemailAccessUri;
163 
164     private int configurationState = Status.CONFIGURATION_STATE_NOT_CONFIGURED;
165     private int dataChannelState = Status.DATA_CHANNEL_STATE_NO_CONNECTION;
166     private int notificationChannelState = Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION;
167 
168     private int quotaOccupied = Status.QUOTA_UNAVAILABLE;
169     private int quotaTotal = Status.QUOTA_UNAVAILABLE;
170 
171     private boolean isAirplaneMode;
172 
build()173     public VoicemailStatus build() {
174       return new VoicemailStatus(this);
175     }
176 
setSourcePackage(String sourcePackage)177     public Builder setSourcePackage(String sourcePackage) {
178       this.sourcePackage = sourcePackage;
179       return this;
180     }
181 
setType(String type)182     public Builder setType(String type) {
183       this.type = type;
184       return this;
185     }
186 
setPhoneAccountComponentName(String name)187     public Builder setPhoneAccountComponentName(String name) {
188       this.phoneAccountComponentName = name;
189       return this;
190     }
191 
setPhoneAccountId(String id)192     public Builder setPhoneAccountId(String id) {
193       this.phoneAccountId = id;
194       return this;
195     }
196 
setSettingsUri(Uri settingsUri)197     public Builder setSettingsUri(Uri settingsUri) {
198       this.settingsUri = settingsUri;
199       return this;
200     }
201 
setVoicemailAccessUri(Uri voicemailAccessUri)202     public Builder setVoicemailAccessUri(Uri voicemailAccessUri) {
203       this.voicemailAccessUri = voicemailAccessUri;
204       return this;
205     }
206 
setConfigurationState(int configurationState)207     public Builder setConfigurationState(int configurationState) {
208       this.configurationState = configurationState;
209       return this;
210     }
211 
setDataChannelState(int dataChannelState)212     public Builder setDataChannelState(int dataChannelState) {
213       this.dataChannelState = dataChannelState;
214       return this;
215     }
216 
setNotificationChannelState(int notificationChannelState)217     public Builder setNotificationChannelState(int notificationChannelState) {
218       this.notificationChannelState = notificationChannelState;
219       return this;
220     }
221 
setQuotaOccupied(int quotaOccupied)222     public Builder setQuotaOccupied(int quotaOccupied) {
223       this.quotaOccupied = quotaOccupied;
224       return this;
225     }
226 
setQuotaTotal(int quotaTotal)227     public Builder setQuotaTotal(int quotaTotal) {
228       this.quotaTotal = quotaTotal;
229       return this;
230     }
231 
setAirplaneMode(boolean isAirplaneMode)232     public Builder setAirplaneMode(boolean isAirplaneMode) {
233       this.isAirplaneMode = isAirplaneMode;
234       return this;
235     }
236   }
237 
isActive()238   public boolean isActive() {
239     switch (configurationState) {
240       case Status.CONFIGURATION_STATE_NOT_CONFIGURED:
241       case Status.CONFIGURATION_STATE_DISABLED:
242         return false;
243       default:
244         return true;
245     }
246   }
247 
248   @Override
toString()249   public String toString() {
250     return "VoicemailStatus["
251         + "sourcePackage: "
252         + sourcePackage
253         + ", type:"
254         + type
255         + ", settingsUri: "
256         + settingsUri
257         + ", voicemailAccessUri: "
258         + voicemailAccessUri
259         + ", configurationState: "
260         + configurationState
261         + ", dataChannelState: "
262         + dataChannelState
263         + ", notificationChannelState: "
264         + notificationChannelState
265         + ", quotaOccupied: "
266         + quotaOccupied
267         + ", quotaTotal: "
268         + quotaTotal
269         + ", isAirplaneMode: "
270         + isAirplaneMode
271         + "]";
272   }
273 
274   @Nullable
getUri(Cursor cursor, int index)275   private static Uri getUri(Cursor cursor, int index) {
276     if (cursor.getString(index) != null) {
277       return Uri.parse(cursor.getString(index));
278     }
279     return null;
280   }
281 
getInt(Cursor cursor, int index, int defaultValue)282   private static int getInt(Cursor cursor, int index, int defaultValue) {
283     if (cursor.isNull(index)) {
284       return defaultValue;
285     }
286     return cursor.getInt(index);
287   }
288 
getString(Cursor cursor, int index, String defaultValue)289   private static String getString(Cursor cursor, int index, String defaultValue) {
290     if (cursor.isNull(index)) {
291       return defaultValue;
292     }
293     return cursor.getString(index);
294   }
295 
getPhoneAccountHandle()296   public PhoneAccountHandle getPhoneAccountHandle() {
297     return new PhoneAccountHandle(
298         ComponentName.unflattenFromString(phoneAccountComponentName), phoneAccountId);
299   }
300 }
301