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.settings.network.telephony;
18 
19 import android.app.AlertDialog;
20 import android.app.Dialog;
21 import android.content.Context;
22 import android.content.DialogInterface;
23 import android.os.PersistableBundle;
24 import android.telephony.CarrierConfigManager;
25 import android.telephony.SubscriptionManager;
26 import android.telephony.TelephonyCallback;
27 import android.telephony.TelephonyManager;
28 import android.telephony.ims.ImsMmTelManager;
29 import android.util.Log;
30 
31 import androidx.annotation.VisibleForTesting;
32 import androidx.preference.Preference;
33 import androidx.preference.PreferenceScreen;
34 import androidx.preference.TwoStatePreference;
35 
36 import com.android.internal.telephony.flags.Flags;
37 import com.android.internal.telephony.util.ArrayUtils;
38 import com.android.settings.R;
39 import com.android.settings.network.ims.VolteQueryImsState;
40 import com.android.settingslib.core.lifecycle.LifecycleObserver;
41 import com.android.settingslib.core.lifecycle.events.OnStart;
42 import com.android.settingslib.core.lifecycle.events.OnStop;
43 
44 import java.util.ArrayList;
45 import java.util.List;
46 
47 /**
48  * Preference controller for "Enhanced 4G LTE"
49  */
50 public class Enhanced4gBasePreferenceController extends TelephonyTogglePreferenceController
51         implements LifecycleObserver, OnStart, OnStop {
52 
53     private static final String TAG = "Enhanced4g";
54 
55     @VisibleForTesting
56     Preference mPreference;
57     private PhoneCallStateTelephonyCallback mTelephonyCallback;
58     private boolean mShow5gLimitedDialog;
59     boolean mIsNrEnabledFromCarrierConfig;
60     private boolean mHas5gCapability;
61     private Integer mCallState;
62     private final List<On4gLteUpdateListener> m4gLteListeners;
63 
64     protected static final int MODE_NONE = -1;
65     protected static final int MODE_VOLTE = 0;
66     protected static final int MODE_ADVANCED_CALL = 1;
67     protected static final int MODE_4G_CALLING = 2;
68     private int m4gCurrentMode = MODE_NONE;
69 
Enhanced4gBasePreferenceController(Context context, String key)70     public Enhanced4gBasePreferenceController(Context context, String key) {
71         super(context, key);
72         m4gLteListeners = new ArrayList<>();
73     }
74 
init(int subId)75     public Enhanced4gBasePreferenceController init(int subId) {
76         if (mTelephonyCallback == null) {
77             mTelephonyCallback = new PhoneCallStateTelephonyCallback();
78         }
79 
80         mSubId = subId;
81         final PersistableBundle carrierConfig = getCarrierConfigForSubId(subId);
82         if (carrierConfig == null) {
83             return this;
84         }
85 
86         final boolean show4GForLTE = carrierConfig.getBoolean(
87                 CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL);
88         m4gCurrentMode = carrierConfig.getInt(
89                 CarrierConfigManager.KEY_ENHANCED_4G_LTE_TITLE_VARIANT_INT);
90         if (m4gCurrentMode != MODE_ADVANCED_CALL) {
91             m4gCurrentMode = show4GForLTE ? MODE_4G_CALLING : MODE_VOLTE;
92         }
93 
94         mShow5gLimitedDialog = carrierConfig.getBoolean(
95                 CarrierConfigManager.KEY_VOLTE_5G_LIMITED_ALERT_DIALOG_BOOL);
96 
97         int[] nrAvailabilities = carrierConfig.getIntArray(
98                 CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY);
99         mIsNrEnabledFromCarrierConfig = !ArrayUtils.isEmpty(nrAvailabilities);
100         return this;
101     }
102 
103     @Override
getAvailabilityStatus(int subId)104     public int getAvailabilityStatus(int subId) {
105         init(subId);
106         if (!isModeMatched()) {
107             return CONDITIONALLY_UNAVAILABLE;
108         }
109         final VolteQueryImsState queryState = queryImsState(subId);
110         // Show VoLTE settings if VoIMS opt-in has been enabled irrespective of other VoLTE settings
111         if (queryState.isVoImsOptInEnabled()) {
112             return AVAILABLE;
113         }
114 
115         final PersistableBundle carrierConfig = getCarrierConfigForSubId(subId);
116         if ((carrierConfig == null)
117                 || carrierConfig.getBoolean(CarrierConfigManager.KEY_HIDE_ENHANCED_4G_LTE_BOOL)) {
118             return CONDITIONALLY_UNAVAILABLE;
119         }
120 
121         if (!queryState.isReadyToVoLte()) {
122             return CONDITIONALLY_UNAVAILABLE;
123         }
124         return (isUserControlAllowed(carrierConfig) && queryState.isAllowUserControl())
125                 ? AVAILABLE : AVAILABLE_UNSEARCHABLE;
126     }
127 
128     @Override
displayPreference(PreferenceScreen screen)129     public void displayPreference(PreferenceScreen screen) {
130         super.displayPreference(screen);
131         mPreference = screen.findPreference(getPreferenceKey());
132     }
133 
134     @Override
onStart()135     public void onStart() {
136         if (mTelephonyCallback == null) {
137             return;
138         }
139         mTelephonyCallback.register(mContext, mSubId);
140     }
141 
142     @Override
onStop()143     public void onStop() {
144         if (mTelephonyCallback == null) {
145             return;
146         }
147         mTelephonyCallback.unregister();
148     }
149 
150     @Override
updateState(Preference preference)151     public void updateState(Preference preference) {
152         super.updateState(preference);
153         if (preference == null) {
154             return;
155         }
156         final TwoStatePreference switchPreference = (TwoStatePreference) preference;
157 
158         final VolteQueryImsState queryState = queryImsState(mSubId);
159         switchPreference.setEnabled(isUserControlAllowed(getCarrierConfigForSubId(mSubId))
160                 && queryState.isAllowUserControl());
161         switchPreference.setChecked(queryState.isEnabledByUser()
162                 && queryState.isAllowUserControl());
163     }
164 
165     @Override
setChecked(boolean isChecked)166     public boolean setChecked(boolean isChecked) {
167         if (!SubscriptionManager.isValidSubscriptionId(mSubId)) {
168             return false;
169         }
170         final ImsMmTelManager imsMmTelManager = ImsMmTelManager.createForSubscriptionId(mSubId);
171         if (imsMmTelManager == null) {
172             return false;
173         }
174 
175         if (isDialogNeeded() && !isChecked) {
176             show5gLimitedDialog(imsMmTelManager);
177         } else {
178             return setAdvancedCallingSettingEnabled(imsMmTelManager, isChecked);
179         }
180         return false;
181     }
182 
183     @Override
isChecked()184     public boolean isChecked() {
185         final VolteQueryImsState queryState = queryImsState(mSubId);
186         return queryState.isEnabledByUser();
187     }
188 
addListener(On4gLteUpdateListener lsn)189     public Enhanced4gBasePreferenceController addListener(On4gLteUpdateListener lsn) {
190         m4gLteListeners.add(lsn);
191         return this;
192     }
193 
194     @VisibleForTesting
getMode()195     protected int getMode() {
196         return MODE_NONE;
197     }
198 
isModeMatched()199     private boolean isModeMatched() {
200         return m4gCurrentMode == getMode();
201     }
202 
203     @VisibleForTesting
queryImsState(int subId)204     protected VolteQueryImsState queryImsState(int subId) {
205         return new VolteQueryImsState(mContext, subId);
206     }
207 
208     @VisibleForTesting
isCallStateIdle()209     protected boolean isCallStateIdle() {
210         return (mCallState != null) && (mCallState == TelephonyManager.CALL_STATE_IDLE);
211     }
212 
isUserControlAllowed(final PersistableBundle carrierConfig)213     private boolean isUserControlAllowed(final PersistableBundle carrierConfig) {
214         return isCallStateIdle()
215                 && (carrierConfig != null)
216                 && carrierConfig.getBoolean(
217                 CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL);
218     }
219 
220     private class PhoneCallStateTelephonyCallback extends TelephonyCallback implements
221             TelephonyCallback.CallStateListener {
222 
223         private TelephonyManager mTelephonyManager;
224 
225         @Override
onCallStateChanged(int state)226         public void onCallStateChanged(int state) {
227             mCallState = state;
228             updateState(mPreference);
229         }
230 
register(Context context, int subId)231         public void register(Context context, int subId) {
232             mTelephonyManager = context.getSystemService(TelephonyManager.class);
233             if (SubscriptionManager.isValidSubscriptionId(subId)) {
234                 mTelephonyManager = mTelephonyManager.createForSubscriptionId(subId);
235             }
236             // assign current call state so that it helps to show correct preference state even
237             // before first onCallStateChanged() by initial registration.
238             if (Flags.enforceTelephonyFeatureMappingForPublicApis()) {
239                 try {
240                     mCallState = mTelephonyManager.getCallState(subId);
241                 } catch (UnsupportedOperationException e) {
242                     // Device doesn't support FEATURE_TELEPHONY_CALLING
243                     mCallState = TelephonyManager.CALL_STATE_IDLE;
244                 }
245             } else {
246                 mCallState = mTelephonyManager.getCallState(subId);
247             }
248             mTelephonyManager.registerTelephonyCallback(
249                     mContext.getMainExecutor(), mTelephonyCallback);
250 
251             final long supportedRadioBitmask = mTelephonyManager.getSupportedRadioAccessFamily();
252             mHas5gCapability =
253                     (supportedRadioBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_NR) > 0;
254         }
255 
unregister()256         public void unregister() {
257             mCallState = null;
258             if (mTelephonyManager != null) {
259                 mTelephonyManager.unregisterTelephonyCallback(this);
260             }
261         }
262     }
263 
264     /**
265      * Update other preferences when 4gLte state is changed
266      */
267     public interface On4gLteUpdateListener {
on4gLteUpdated()268         void on4gLteUpdated();
269     }
270 
isDialogNeeded()271     private boolean isDialogNeeded() {
272         Log.d(TAG, "Has5gCapability:" + mHas5gCapability);
273         return mShow5gLimitedDialog && mHas5gCapability && mIsNrEnabledFromCarrierConfig;
274     }
275 
show5gLimitedDialog(ImsMmTelManager imsMmTelManager)276     private void show5gLimitedDialog(ImsMmTelManager imsMmTelManager) {
277         Log.d(TAG, "show5gLimitedDialog");
278         AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
279         DialogInterface.OnClickListener networkSettingsClickListener =
280                 new Dialog.OnClickListener() {
281                     @Override
282                     public void onClick(DialogInterface dialog, int which) {
283                         Log.d(TAG, "onClick,isChecked:false");
284                         setAdvancedCallingSettingEnabled(imsMmTelManager, false);
285                         updateState(mPreference);
286                     }
287                 };
288         builder.setTitle(R.string.volte_5G_limited_title)
289                 .setMessage(R.string.volte_5G_limited_text)
290                 .setNeutralButton(mContext.getResources().getString(
291                         R.string.cancel), null)
292                 .setPositiveButton(mContext.getResources().getString(
293                         R.string.condition_turn_off),
294                         networkSettingsClickListener)
295                 .create()
296                 .show();
297     }
298 
setAdvancedCallingSettingEnabled(ImsMmTelManager imsMmTelManager, boolean isChecked)299     private boolean setAdvancedCallingSettingEnabled(ImsMmTelManager imsMmTelManager,
300             boolean isChecked) {
301         try {
302             imsMmTelManager.setAdvancedCallingSettingEnabled(isChecked);
303         } catch (IllegalArgumentException exception) {
304             Log.w(TAG, "fail to set VoLTE=" + isChecked + ". subId=" + mSubId, exception);
305             return false;
306         }
307         for (final On4gLteUpdateListener lsn : m4gLteListeners) {
308             lsn.on4gLteUpdated();
309         }
310         return true;
311     }
312 }
313