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                 /* neutralListener= */ null);
81     }
82 
83     @Override
onStopInternal()84     protected void onStopInternal() {
85         mRoamingStateChangeObserver.unregister(getContext());
86     }
87 
88     @Override
updateState(TwoStatePreference preference)89     protected void updateState(TwoStatePreference preference) {
90         preference.setEnabled(mSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID);
91         preference.setChecked(
92                 mTelephonyManager != null ? mTelephonyManager.isDataRoamingEnabled() : false);
93     }
94 
95     @Override
handlePreferenceChanged(TwoStatePreference preference, Object newValue)96     protected boolean handlePreferenceChanged(TwoStatePreference preference, Object newValue) {
97         boolean isEnabled = (boolean) newValue;
98         if (isEnabled && isDialogNeeded()) {
99             getFragmentController().showDialog(getRoamingAlertDialog(),
100                     ConfirmationDialogFragment.TAG);
101             return false;
102         }
103 
104         setRoamingEnabled(isEnabled);
105         return true;
106     }
107 
108     @Override
onMobileNetworkUpdated(int subId)109     public void onMobileNetworkUpdated(int subId) {
110         setSubId(subId);
111         refreshUi();
112     }
113 
setRoamingEnabled(boolean enabled)114     private void setRoamingEnabled(boolean enabled) {
115         mTelephonyManager.setDataRoamingEnabled(enabled);
116         refreshUi();
117     }
118 
isDialogNeeded()119     private boolean isDialogNeeded() {
120         boolean isRoamingEnabled = mTelephonyManager.isDataRoamingEnabled();
121         PersistableBundle carrierConfig = mCarrierConfigManager.getConfigForSubId(mSubId);
122 
123         // Need dialog if we need to turn on roaming and the roaming charge indication is allowed.
124         if (!isRoamingEnabled && (carrierConfig == null || !carrierConfig.getBoolean(
125                 CarrierConfigManager.KEY_DISABLE_CHARGE_INDICATION_BOOL))) {
126             return true;
127         }
128         return false;
129     }
130 
getRoamingAlertDialog()131     private ConfirmationDialogFragment getRoamingAlertDialog() {
132         return new ConfirmationDialogFragment.Builder(getContext())
133                 .setTitle(R.string.roaming_alert_title)
134                 .setMessage(R.string.roaming_warning)
135                 .setPositiveButton(android.R.string.yes, mConfirmListener)
136                 .setNegativeButton(android.R.string.no, /* rejectListener= */ null)
137                 .build();
138     }
139 
140     /** Observer that listens to data roaming change. */
141     private static class RoamingStateChangeObserver extends ContentObserver {
142 
143         private Runnable mChangeListener;
144 
RoamingStateChangeObserver(Handler handler, Runnable changeListener)145         RoamingStateChangeObserver(Handler handler, Runnable changeListener) {
146             super(handler);
147             mChangeListener = changeListener;
148         }
149 
150         @Override
onChange(boolean selfChange)151         public void onChange(boolean selfChange) {
152             super.onChange(selfChange);
153             mChangeListener.run();
154         }
155 
156         /** Register this observer to listen for updates to {@link Settings.Global#DATA_ROAMING}. */
register(Context context, int subId)157         public void register(Context context, int subId) {
158             Uri uri = Settings.Global.getUriFor(Settings.Global.DATA_ROAMING);
159             if (TelephonyManager.from(context).getSimCount() != 1) {
160                 uri = Settings.Global.getUriFor(Settings.Global.DATA_ROAMING + subId);
161             }
162             context.getContentResolver().registerContentObserver(uri,
163                     /* notifyForDescendants= */ false, /* observer= */ this);
164         }
165 
166         /** Unregister this observer. */
unregister(Context context)167         public void unregister(Context context) {
168             context.getContentResolver().unregisterContentObserver(/* observer= */ this);
169         }
170     }
171 }
172