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.settings.network;
18 
19 import android.car.drivingstate.CarUxRestrictions;
20 import android.content.Context;
21 import android.database.ContentObserver;
22 import android.net.Uri;
23 import android.os.Handler;
24 import android.os.Looper;
25 import android.os.PersistableBundle;
26 import android.provider.Settings;
27 import android.telephony.CarrierConfigManager;
28 import android.telephony.SubscriptionManager;
29 import android.telephony.TelephonyManager;
30 
31 import androidx.preference.TwoStatePreference;
32 
33 import com.android.car.settings.R;
34 import com.android.car.settings.common.ConfirmationDialogFragment;
35 import com.android.car.settings.common.FragmentController;
36 import com.android.car.settings.common.PreferenceController;
37 
38 /** Business logic for toggling the roaming state. */
39 // TODO: This preference should be available but unsearchable if subscription id is invalid.
40 public class RoamingPreferenceController extends PreferenceController<TwoStatePreference> implements
41         MobileNetworkUpdateManager.MobileNetworkUpdateListener {
42 
43     private final CarrierConfigManager mCarrierConfigManager;
44     private final RoamingStateChangeObserver mRoamingStateChangeObserver;
45     private TelephonyManager mTelephonyManager;
46     private int mSubId;
47 
48     private final ConfirmationDialogFragment.ConfirmListener mConfirmListener =
49             arguments -> setRoamingEnabled(true);
50 
RoamingPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)51     public RoamingPreferenceController(Context context, String preferenceKey,
52             FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
53         super(context, preferenceKey, fragmentController, uxRestrictions);
54         mCarrierConfigManager = context.getSystemService(CarrierConfigManager.class);
55         mRoamingStateChangeObserver = new RoamingStateChangeObserver(
56                 new Handler(Looper.getMainLooper()), this::refreshUi);
57         mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
58     }
59 
60     @Override
getPreferenceType()61     protected Class<TwoStatePreference> getPreferenceType() {
62         return TwoStatePreference.class;
63     }
64 
65     /** Set the subscription id for which the roaming toggle should take effect. */
setSubId(int subId)66     public void setSubId(int subId) {
67         mSubId = subId;
68         mTelephonyManager = TelephonyManager.from(getContext()).createForSubscriptionId(mSubId);
69     }
70 
71     @Override
onStartInternal()72     protected void onStartInternal() {
73         mRoamingStateChangeObserver.register(getContext(), mSubId);
74 
75         ConfirmationDialogFragment.resetListeners(
76                 (ConfirmationDialogFragment) getFragmentController().findDialogByTag(
77                         ConfirmationDialogFragment.TAG),
78                 mConfirmListener,
79                 /* rejectListener= */ null);
80     }
81 
82     @Override
onStopInternal()83     protected void onStopInternal() {
84         mRoamingStateChangeObserver.unregister(getContext());
85     }
86 
87     @Override
updateState(TwoStatePreference preference)88     protected void updateState(TwoStatePreference preference) {
89         preference.setEnabled(mSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID);
90         preference.setChecked(
91                 mTelephonyManager != null ? mTelephonyManager.isDataRoamingEnabled() : false);
92     }
93 
94     @Override
handlePreferenceChanged(TwoStatePreference preference, Object newValue)95     protected boolean handlePreferenceChanged(TwoStatePreference preference, Object newValue) {
96         boolean isEnabled = (boolean) newValue;
97         if (isEnabled && isDialogNeeded()) {
98             getFragmentController().showDialog(getRoamingAlertDialog(),
99                     ConfirmationDialogFragment.TAG);
100             return false;
101         }
102 
103         setRoamingEnabled(isEnabled);
104         return true;
105     }
106 
107     @Override
onMobileNetworkUpdated(int subId)108     public void onMobileNetworkUpdated(int subId) {
109         setSubId(subId);
110         refreshUi();
111     }
112 
setRoamingEnabled(boolean enabled)113     private void setRoamingEnabled(boolean enabled) {
114         mTelephonyManager.setDataRoamingEnabled(enabled);
115         refreshUi();
116     }
117 
isDialogNeeded()118     private boolean isDialogNeeded() {
119         boolean isRoamingEnabled = mTelephonyManager.isDataRoamingEnabled();
120         PersistableBundle carrierConfig = mCarrierConfigManager.getConfigForSubId(mSubId);
121 
122         // Need dialog if we need to turn on roaming and the roaming charge indication is allowed.
123         if (!isRoamingEnabled && (carrierConfig == null || !carrierConfig.getBoolean(
124                 CarrierConfigManager.KEY_DISABLE_CHARGE_INDICATION_BOOL))) {
125             return true;
126         }
127         return false;
128     }
129 
getRoamingAlertDialog()130     private ConfirmationDialogFragment getRoamingAlertDialog() {
131         return new ConfirmationDialogFragment.Builder(getContext())
132                 .setTitle(R.string.roaming_alert_title)
133                 .setMessage(R.string.roaming_warning)
134                 .setPositiveButton(android.R.string.yes, mConfirmListener)
135                 .setNegativeButton(android.R.string.no, /* rejectListener= */ null)
136                 .build();
137     }
138 
139     /** Observer that listens to data roaming change. */
140     private static class RoamingStateChangeObserver extends ContentObserver {
141 
142         private Runnable mChangeListener;
143 
RoamingStateChangeObserver(Handler handler, Runnable changeListener)144         RoamingStateChangeObserver(Handler handler, Runnable changeListener) {
145             super(handler);
146             mChangeListener = changeListener;
147         }
148 
149         @Override
onChange(boolean selfChange)150         public void onChange(boolean selfChange) {
151             super.onChange(selfChange);
152             mChangeListener.run();
153         }
154 
155         /** Register this observer to listen for updates to {@link Settings.Global#DATA_ROAMING}. */
register(Context context, int subId)156         public void register(Context context, int subId) {
157             Uri uri = Settings.Global.getUriFor(Settings.Global.DATA_ROAMING);
158             if (TelephonyManager.from(context).getSimCount() != 1) {
159                 uri = Settings.Global.getUriFor(Settings.Global.DATA_ROAMING + subId);
160             }
161             context.getContentResolver().registerContentObserver(uri,
162                     /* notifyForDescendants= */ false, /* observer= */ this);
163         }
164 
165         /** Unregister this observer. */
unregister(Context context)166         public void unregister(Context context) {
167             context.getContentResolver().unregisterContentObserver(/* observer= */ this);
168         }
169     }
170 }
171