• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.settings.wifi.calling;
18 
19 import static android.app.slice.Slice.EXTRA_TOGGLE_STATE;
20 
21 import android.app.PendingIntent;
22 import android.content.ComponentName;
23 import android.content.ContentResolver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.net.Uri;
27 import android.os.PersistableBundle;
28 import android.provider.Settings;
29 import android.support.v4.graphics.drawable.IconCompat;
30 import android.telephony.CarrierConfigManager;
31 import android.telephony.SubscriptionManager;
32 import android.telephony.TelephonyManager;
33 import android.text.TextUtils;
34 import android.util.Log;
35 
36 import androidx.slice.Slice;
37 import androidx.slice.builders.ListBuilder;
38 import androidx.slice.builders.SliceAction;
39 
40 import com.android.ims.ImsManager;
41 import com.android.internal.annotations.VisibleForTesting;
42 import com.android.settings.R;
43 import com.android.settings.slices.SettingsSliceProvider;
44 import com.android.settings.slices.SliceBroadcastReceiver;
45 import com.android.settings.slices.SliceBuilderUtils;
46 
47 import java.util.concurrent.Callable;
48 import java.util.concurrent.ExecutionException;
49 import java.util.concurrent.ExecutorService;
50 import java.util.concurrent.Executors;
51 import java.util.concurrent.FutureTask;
52 import java.util.concurrent.TimeUnit;
53 import java.util.concurrent.TimeoutException;
54 
55 
56 /**
57  * Helper class to control slices for wifi calling settings.
58  */
59 public class WifiCallingSliceHelper {
60 
61     private static final String TAG = "WifiCallingSliceHelper";
62 
63     /**
64      * Settings slice path to wifi calling setting.
65      */
66     public static final String PATH_WIFI_CALLING = "wifi_calling";
67 
68     /**
69      * Action passed for changes to wifi calling slice (toggle).
70      */
71     public static final String ACTION_WIFI_CALLING_CHANGED =
72             "com.android.settings.wifi.calling.action.WIFI_CALLING_CHANGED";
73 
74     /**
75      * Action for Wifi calling Settings activity which
76      * allows setting configuration for Wifi calling
77      * related settings
78      */
79     public static final String ACTION_WIFI_CALLING_SETTINGS_ACTIVITY =
80             "android.settings.WIFI_CALLING_SETTINGS";
81 
82     /**
83      * Full {@link Uri} for the Wifi Calling Slice.
84      */
85     public static final Uri WIFI_CALLING_URI = new Uri.Builder()
86             .scheme(ContentResolver.SCHEME_CONTENT)
87             .authority(SettingsSliceProvider.SLICE_AUTHORITY)
88             .appendPath(PATH_WIFI_CALLING)
89             .build();
90 
91     /**
92      * Timeout for querying wifi calling setting from ims manager.
93      */
94     private static final int TIMEOUT_MILLIS = 2000;
95 
96     protected SubscriptionManager mSubscriptionManager;
97     private final Context mContext;
98 
99     @VisibleForTesting
WifiCallingSliceHelper(Context context)100     public WifiCallingSliceHelper(Context context) {
101         mContext = context;
102     }
103 
104     /**
105      * Returns Slice object for wifi calling settings.
106      *
107      * If wifi calling is being turned on and if wifi calling activation is needed for the current
108      * carrier, this method will return Slice with instructions to go to Settings App.
109      *
110      * If wifi calling is not supported for the current carrier, this method will return slice with
111      * not supported message.
112      *
113      * If wifi calling setting can be changed, this method will return the slice to toggle wifi
114      * calling option with ACTION_WIFI_CALLING_CHANGED as endItem.
115      */
createWifiCallingSlice(Uri sliceUri)116     public Slice createWifiCallingSlice(Uri sliceUri) {
117         final int subId = getDefaultVoiceSubId();
118         final String carrierName = getSimCarrierName();
119 
120         if (subId <= SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
121             Log.d(TAG, "Invalid subscription Id");
122             return getNonActionableWifiCallingSlice(
123                     mContext.getString(R.string.wifi_calling_settings_title),
124                     mContext.getString(R.string.wifi_calling_not_supported, carrierName),
125                     sliceUri, getSettingsIntent(mContext));
126         }
127 
128         final ImsManager imsManager = getImsManager(subId);
129 
130         if (!imsManager.isWfcEnabledByPlatform()
131                 || !imsManager.isWfcProvisionedOnDevice()) {
132             Log.d(TAG, "Wifi calling is either not provisioned or not enabled by Platform");
133             return getNonActionableWifiCallingSlice(
134                     mContext.getString(R.string.wifi_calling_settings_title),
135                     mContext.getString(R.string.wifi_calling_not_supported, carrierName),
136                     sliceUri, getSettingsIntent(mContext));
137         }
138 
139         try {
140             final boolean isWifiCallingEnabled = isWifiCallingEnabled(imsManager);
141             final Intent activationAppIntent =
142                     getWifiCallingCarrierActivityIntent(subId);
143 
144             // Send this actionable wifi calling slice to toggle the setting
145             // only when there is no need for wifi calling activation with the server
146             if (activationAppIntent != null && !isWifiCallingEnabled) {
147                 Log.d(TAG, "Needs Activation");
148                 // Activation needed for the next action of the user
149                 // Give instructions to go to settings app
150                 return getNonActionableWifiCallingSlice(
151                         mContext.getString(R.string.wifi_calling_settings_title),
152                         mContext.getString(
153                                 R.string.wifi_calling_settings_activation_instructions),
154                         sliceUri, getActivityIntent(ACTION_WIFI_CALLING_SETTINGS_ACTIVITY));
155             }
156             return getWifiCallingSlice(sliceUri, mContext, isWifiCallingEnabled);
157         } catch (InterruptedException | TimeoutException | ExecutionException e) {
158             Log.e(TAG, "Unable to read the current WiFi calling status", e);
159             return getNonActionableWifiCallingSlice(
160                     mContext.getString(R.string.wifi_calling_settings_title),
161                     mContext.getString(R.string.wifi_calling_turn_on),
162                     sliceUri, getActivityIntent(ACTION_WIFI_CALLING_SETTINGS_ACTIVITY));
163         }
164     }
165 
isWifiCallingEnabled(ImsManager imsManager)166     private boolean isWifiCallingEnabled(ImsManager imsManager)
167             throws InterruptedException, ExecutionException, TimeoutException {
168         final FutureTask<Boolean> isWifiOnTask = new FutureTask<>(new Callable<Boolean>() {
169             @Override
170             public Boolean call() {
171                 return imsManager.isWfcEnabledByUser();
172             }
173         });
174         final ExecutorService executor = Executors.newSingleThreadExecutor();
175         executor.execute(isWifiOnTask);
176 
177         Boolean isWifiEnabledByUser = false;
178         isWifiEnabledByUser = isWifiOnTask.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
179 
180         return isWifiEnabledByUser && imsManager.isNonTtyOrTtyOnVolteEnabled();
181     }
182 
183     /**
184      * Builds a toggle slice where the intent takes you to the wifi calling page and the toggle
185      * enables/disables wifi calling.
186      */
getWifiCallingSlice(Uri sliceUri, Context mContext, boolean isWifiCallingEnabled)187     private Slice getWifiCallingSlice(Uri sliceUri, Context mContext,
188             boolean isWifiCallingEnabled) {
189 
190         final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.wifi_signal);
191         final String title = mContext.getString(R.string.wifi_calling_settings_title);
192         return new ListBuilder(mContext, sliceUri, ListBuilder.INFINITY)
193                 .setColor(R.color.material_blue_500)
194                 .addRow(b -> b
195                         .setTitle(title)
196                         .addEndItem(
197                                 new SliceAction(
198                                         getBroadcastIntent(ACTION_WIFI_CALLING_CHANGED),
199                                         null /* actionTitle */, isWifiCallingEnabled))
200                         .setPrimaryAction(new SliceAction(
201                                 getActivityIntent(ACTION_WIFI_CALLING_SETTINGS_ACTIVITY),
202                                 icon,
203                                 title)))
204                 .build();
205     }
206 
getImsManager(int subId)207     protected ImsManager getImsManager(int subId) {
208         return ImsManager.getInstance(mContext, SubscriptionManager.getPhoneId(subId));
209     }
210 
getWfcMode(ImsManager imsManager)211     private Integer getWfcMode(ImsManager imsManager)
212             throws InterruptedException, ExecutionException, TimeoutException {
213         FutureTask<Integer> wfcModeTask = new FutureTask<>(new Callable<Integer>() {
214             @Override
215             public Integer call() {
216                 return imsManager.getWfcMode(false);
217             }
218         });
219         ExecutorService executor = Executors.newSingleThreadExecutor();
220         executor.execute(wfcModeTask);
221         return wfcModeTask.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
222     }
223 
224     /**
225      * Handles wifi calling setting change from wifi calling slice and posts notification. Should be
226      * called when intent action is ACTION_WIFI_CALLING_CHANGED. Executed in @WorkerThread
227      *
228      * @param intent action performed
229      */
handleWifiCallingChanged(Intent intent)230     public void handleWifiCallingChanged(Intent intent) {
231         final int subId = getDefaultVoiceSubId();
232 
233         if (subId > SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
234             final ImsManager imsManager = getImsManager(subId);
235             if (imsManager.isWfcEnabledByPlatform()
236                     || imsManager.isWfcProvisionedOnDevice()) {
237                 final boolean currentValue = imsManager.isWfcEnabledByUser()
238                         && imsManager.isNonTtyOrTtyOnVolteEnabled();
239                 final boolean newValue = intent.getBooleanExtra(EXTRA_TOGGLE_STATE,
240                         currentValue);
241                 final Intent activationAppIntent =
242                         getWifiCallingCarrierActivityIntent(subId);
243                 if (!newValue || activationAppIntent == null) {
244                     // If either the action is to turn off wifi calling setting
245                     // or there is no activation involved - Update the setting
246                     if (newValue != currentValue) {
247                         imsManager.setWfcSetting(newValue);
248                     }
249                 }
250             }
251         }
252         // notify change in slice in any case to get re-queried. This would result in displaying
253         // appropriate message with the updated setting.
254         final Uri uri = SliceBuilderUtils.getUri(PATH_WIFI_CALLING, false /*isPlatformSlice*/);
255         mContext.getContentResolver().notifyChange(uri, null);
256     }
257 
258     /**
259      * Returns Slice with the title and subtitle provided as arguments with wifi signal Icon.
260      *
261      * @param title Title of the slice
262      * @param subtitle Subtitle of the slice
263      * @param sliceUri slice uri
264      * @return Slice with title and subtitle
265      */
266     // TODO(b/79548264) asses different scenarios and return null instead of non-actionable slice
getNonActionableWifiCallingSlice(String title, String subtitle, Uri sliceUri, PendingIntent primaryActionIntent)267     private Slice getNonActionableWifiCallingSlice(String title, String subtitle, Uri sliceUri,
268             PendingIntent primaryActionIntent) {
269         final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.wifi_signal);
270         return new ListBuilder(mContext, sliceUri, ListBuilder.INFINITY)
271                 .setColor(R.color.material_blue_500)
272                 .addRow(b -> b
273                         .setTitle(title)
274                         .setSubtitle(subtitle)
275                         .setPrimaryAction(new SliceAction(
276                                 primaryActionIntent, icon,
277                                 title)))
278                 .build();
279     }
280 
281     /**
282      * Returns {@code true} when the key is enabled for the carrier, and {@code false} otherwise.
283      */
isCarrierConfigManagerKeyEnabled(Context mContext, String key, int subId, boolean defaultValue)284     private boolean isCarrierConfigManagerKeyEnabled(Context mContext, String key,
285             int subId, boolean defaultValue) {
286         final CarrierConfigManager configManager = getCarrierConfigManager(mContext);
287         boolean ret = false;
288         if (configManager != null) {
289             final PersistableBundle bundle = configManager.getConfigForSubId(subId);
290             if (bundle != null) {
291                 ret = bundle.getBoolean(key, defaultValue);
292             }
293         }
294         return ret;
295     }
296 
getCarrierConfigManager(Context mContext)297     protected CarrierConfigManager getCarrierConfigManager(Context mContext) {
298         return mContext.getSystemService(CarrierConfigManager.class);
299     }
300 
301     /**
302      * Returns the current default voice subId obtained from SubscriptionManager
303      */
getDefaultVoiceSubId()304     protected int getDefaultVoiceSubId() {
305         if (mSubscriptionManager == null) {
306             mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class);
307         }
308         return SubscriptionManager.getDefaultVoiceSubscriptionId();
309     }
310 
311     /**
312      * Returns Intent of the activation app required to activate wifi calling or null if there is no
313      * need for activation.
314      */
getWifiCallingCarrierActivityIntent(int subId)315     protected Intent getWifiCallingCarrierActivityIntent(int subId) {
316         final CarrierConfigManager configManager = getCarrierConfigManager(mContext);
317         if (configManager == null) {
318             return null;
319         }
320 
321         final PersistableBundle bundle = configManager.getConfigForSubId(subId);
322         if (bundle == null) {
323             return null;
324         }
325 
326         final String carrierApp = bundle.getString(
327                 CarrierConfigManager.KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING);
328         if (TextUtils.isEmpty(carrierApp)) {
329             return null;
330         }
331 
332         final ComponentName componentName = ComponentName.unflattenFromString(carrierApp);
333         if (componentName == null) {
334             return null;
335         }
336 
337         final Intent intent = new Intent();
338         intent.setComponent(componentName);
339         return intent;
340     }
341 
342     /**
343      * @return {@link PendingIntent} to the Settings home page.
344      */
getSettingsIntent(Context context)345     public static PendingIntent getSettingsIntent(Context context) {
346         final Intent intent = new Intent(Settings.ACTION_SETTINGS);
347         return PendingIntent.getActivity(context, 0 /* requestCode */, intent, 0 /* flags */);
348     }
349 
getBroadcastIntent(String action)350     private PendingIntent getBroadcastIntent(String action) {
351         final Intent intent = new Intent(action);
352         intent.setClass(mContext, SliceBroadcastReceiver.class);
353         return PendingIntent.getBroadcast(mContext, 0 /* requestCode */, intent,
354                 PendingIntent.FLAG_CANCEL_CURRENT);
355     }
356 
357     /**
358      * Returns PendingIntent to start activity specified by action
359      */
getActivityIntent(String action)360     private PendingIntent getActivityIntent(String action) {
361         final Intent intent = new Intent(action);
362         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
363         return PendingIntent.getActivity(mContext, 0 /* requestCode */, intent, 0 /* flags */);
364     }
365 
366     /**
367      * Returns carrier id name of the current Subscription
368      */
getSimCarrierName()369     private String getSimCarrierName() {
370         final TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
371         final CharSequence carrierName = telephonyManager.getSimCarrierIdName();
372         if (carrierName == null) {
373             return mContext.getString(R.string.carrier);
374         }
375         return carrierName.toString();
376     }
377 
378 }
379