1 /*
2  * Copyright (C) 2008 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.apn;
18 
19 import static com.android.settings.network.apn.ApnEditPageProviderKt.INSERT_URL;
20 
21 import android.app.Activity;
22 import android.app.Dialog;
23 import android.app.ProgressDialog;
24 import android.app.settings.SettingsEnums;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.database.Cursor;
28 import android.net.Uri;
29 import android.os.Bundle;
30 import android.os.PersistableBundle;
31 import android.os.UserHandle;
32 import android.os.UserManager;
33 import android.provider.Telephony;
34 import android.telephony.CarrierConfigManager;
35 import android.telephony.SubscriptionManager;
36 import android.telephony.data.ApnSetting;
37 import android.text.TextUtils;
38 import android.util.Log;
39 import android.view.Menu;
40 import android.view.MenuInflater;
41 import android.view.MenuItem;
42 import android.view.MotionEvent;
43 import android.view.View;
44 import android.widget.Toast;
45 
46 import androidx.annotation.NonNull;
47 import androidx.annotation.Nullable;
48 import androidx.lifecycle.LifecycleOwner;
49 import androidx.preference.Preference;
50 import androidx.preference.PreferenceGroup;
51 
52 import com.android.settings.R;
53 import com.android.settings.RestrictedSettingsFragment;
54 import com.android.settings.flags.Flags;
55 import com.android.settings.network.telephony.SubscriptionRepository;
56 import com.android.settings.spa.SpaActivity;
57 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
58 
59 import kotlin.Unit;
60 
61 import java.util.ArrayList;
62 
63 /** Handle each different apn setting. */
64 public class ApnSettings extends RestrictedSettingsFragment
65         implements Preference.OnPreferenceChangeListener {
66     static final String TAG = "ApnSettings";
67 
68     public static final String APN_ID = "apn_id";
69     public static final String APN_LIST = "apn_list";
70     public static final String SUB_ID = "sub_id";
71     public static final String MVNO_TYPE = "mvno_type";
72     public static final String MVNO_MATCH_DATA = "mvno_match_data";
73 
74     private static final String[] CARRIERS_PROJECTION = new String[] {
75             Telephony.Carriers._ID,
76             Telephony.Carriers.NAME,
77             Telephony.Carriers.APN,
78             Telephony.Carriers.TYPE,
79             Telephony.Carriers.MVNO_TYPE,
80             Telephony.Carriers.MVNO_MATCH_DATA,
81             Telephony.Carriers.EDITED_STATUS,
82     };
83 
84     private static final int ID_INDEX = 0;
85     private static final int NAME_INDEX = 1;
86     private static final int APN_INDEX = 2;
87     private static final int TYPES_INDEX = 3;
88     private static final int MVNO_TYPE_INDEX = 4;
89     private static final int MVNO_MATCH_DATA_INDEX = 5;
90     private static final int EDITED_INDEX = 6;
91 
92     private static final int MENU_NEW = Menu.FIRST;
93     private static final int MENU_RESTORE = Menu.FIRST + 1;
94 
95     private static final int DIALOG_RESTORE_DEFAULTAPN = 1001;
96 
97     private boolean mRestoreDefaultApnMode;
98 
99     private UserManager mUserManager;
100     private int mSubId;
101     private PreferredApnRepository mPreferredApnRepository;
102     @Nullable
103     private String mPreferredApnKey;
104     private String mMvnoType;
105     private String mMvnoMatchData;
106 
107     private boolean mUnavailable;
108 
109     private boolean mHideImsApn;
110     private boolean mAllowAddingApns;
111     private boolean mHidePresetApnDetails;
112 
ApnSettings()113     public ApnSettings() {
114         super(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS);
115     }
116 
117     @Override
getMetricsCategory()118     public int getMetricsCategory() {
119         return SettingsEnums.APN;
120     }
121 
122     @Override
onCreate(Bundle icicle)123     public void onCreate(Bundle icicle) {
124         super.onCreate(icicle);
125         final Activity activity = getActivity();
126         mSubId = activity.getIntent().getIntExtra(SUB_ID,
127                 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
128         mPreferredApnRepository = new PreferredApnRepository(activity, mSubId);
129 
130         setIfOnlyAvailableForAdmins(true);
131 
132         final CarrierConfigManager configManager = (CarrierConfigManager)
133                 getSystemService(Context.CARRIER_CONFIG_SERVICE);
134         final PersistableBundle b = configManager.getConfigForSubId(mSubId);
135         mHideImsApn = b.getBoolean(CarrierConfigManager.KEY_HIDE_IMS_APN_BOOL);
136         mAllowAddingApns = b.getBoolean(CarrierConfigManager.KEY_ALLOW_ADDING_APNS_BOOL);
137         if (mAllowAddingApns) {
138             final String[] readOnlyApnTypes = b.getStringArray(
139                     CarrierConfigManager.KEY_READ_ONLY_APN_TYPES_STRING_ARRAY);
140             // if no apn type can be edited, do not allow adding APNs
141             if (ApnEditor.hasAllApns(readOnlyApnTypes)) {
142                 Log.d(TAG, "not allowing adding APN because all APN types are read only");
143                 mAllowAddingApns = false;
144             }
145         }
146         mHidePresetApnDetails = b.getBoolean(CarrierConfigManager.KEY_HIDE_PRESET_APN_DETAILS_BOOL);
147         mUserManager = UserManager.get(activity);
148     }
149 
150     @Override
onActivityCreated(Bundle savedInstanceState)151     public void onActivityCreated(Bundle savedInstanceState) {
152         super.onActivityCreated(savedInstanceState);
153 
154         getEmptyTextView().setText(com.android.settingslib.R.string.apn_settings_not_available);
155         mUnavailable = isUiRestricted();
156         setHasOptionsMenu(!mUnavailable);
157         if (mUnavailable) {
158             addPreferencesFromResource(R.xml.placeholder_prefs);
159             return;
160         }
161 
162         addPreferencesFromResource(R.xml.apn_settings);
163     }
164 
165     @Override
onViewCreated(@onNull View view, @Nullable Bundle savedInstanceState)166     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
167         super.onViewCreated(view, savedInstanceState);
168 
169         LifecycleOwner viewLifecycleOwner = getViewLifecycleOwner();
170         new SubscriptionRepository(requireContext())
171                 .collectSubscriptionEnabled(mSubId, viewLifecycleOwner, (isEnabled) -> {
172                     if (!isEnabled) {
173                         Log.d(TAG, "Due to subscription not enabled, closes APN settings page");
174                         finish();
175                     }
176                     return Unit.INSTANCE;
177                 });
178 
179         mPreferredApnRepository.collectPreferredApn(viewLifecycleOwner, (preferredApn) -> {
180             mPreferredApnKey = preferredApn;
181             final PreferenceGroup apnPreferenceList = findPreference(APN_LIST);
182             for (int i = 0; i < apnPreferenceList.getPreferenceCount(); i++) {
183                 ApnPreference apnPreference = (ApnPreference) apnPreferenceList.getPreference(i);
184                 apnPreference.setIsChecked(apnPreference.getKey().equals(preferredApn));
185             }
186             return Unit.INSTANCE;
187         });
188     }
189 
190     @Override
onResume()191     public void onResume() {
192         super.onResume();
193 
194         if (mUnavailable) {
195             return;
196         }
197 
198         if (!mRestoreDefaultApnMode) {
199             fillList();
200         }
201     }
202 
203     @Override
getRestrictionEnforcedAdmin()204     public EnforcedAdmin getRestrictionEnforcedAdmin() {
205         final UserHandle user = UserHandle.of(mUserManager.getProcessUserId());
206         if (mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, user)
207                 && !mUserManager.hasBaseUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS,
208                         user)) {
209             return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
210         }
211         return null;
212     }
213 
fillList()214     private void fillList() {
215         final Uri simApnUri = Uri.withAppendedPath(Telephony.Carriers.SIM_APN_URI,
216                 String.valueOf(mSubId));
217         final StringBuilder where =
218                 new StringBuilder("NOT (type='ia' AND (apn=\"\" OR apn IS NULL)) AND "
219                 + "user_visible!=0");
220         // Remove Emergency type, users should not mess with that
221         where.append(" AND NOT (type='emergency')");
222 
223         if (mHideImsApn) {
224             where.append(" AND NOT (type='ims')");
225         }
226 
227         final Cursor cursor = getContentResolver().query(simApnUri,
228                 CARRIERS_PROJECTION, where.toString(), null,
229                 Telephony.Carriers.DEFAULT_SORT_ORDER);
230 
231         if (cursor != null) {
232             final PreferenceGroup apnPrefList = findPreference(APN_LIST);
233             apnPrefList.removeAll();
234 
235             final ArrayList<ApnPreference> apnList = new ArrayList<ApnPreference>();
236             final ArrayList<ApnPreference> mmsApnList = new ArrayList<ApnPreference>();
237 
238             cursor.moveToFirst();
239             while (!cursor.isAfterLast()) {
240                 final String name = cursor.getString(NAME_INDEX);
241                 final String apn = cursor.getString(APN_INDEX);
242                 final String key = cursor.getString(ID_INDEX);
243                 final String type = cursor.getString(TYPES_INDEX);
244                 final int edited = cursor.getInt(EDITED_INDEX);
245                 mMvnoType = cursor.getString(MVNO_TYPE_INDEX);
246                 mMvnoMatchData = cursor.getString(MVNO_MATCH_DATA_INDEX);
247 
248                 final ApnPreference pref = new ApnPreference(getPrefContext());
249 
250                 pref.setKey(key);
251                 pref.setTitle(name);
252                 pref.setPersistent(false);
253                 pref.setOnPreferenceChangeListener(this);
254                 pref.setSubId(mSubId);
255                 if (mHidePresetApnDetails && edited == Telephony.Carriers.UNEDITED) {
256                     pref.setHideDetails();
257                 } else {
258                     pref.setSummary(apn);
259                 }
260 
261                 final boolean defaultSelectable =
262                         ((type == null) || type.contains(ApnSetting.TYPE_DEFAULT_STRING));
263                 pref.setDefaultSelectable(defaultSelectable);
264                 if (defaultSelectable) {
265                     pref.setIsChecked(key.equals(mPreferredApnKey));
266                     apnList.add(pref);
267                 } else {
268                     mmsApnList.add(pref);
269                 }
270                 cursor.moveToNext();
271             }
272             cursor.close();
273 
274             for (Preference preference : apnList) {
275                 apnPrefList.addPreference(preference);
276             }
277             for (Preference preference : mmsApnList) {
278                 apnPrefList.addPreference(preference);
279             }
280         }
281     }
282 
283     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)284     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
285         if (!mUnavailable) {
286             if (mAllowAddingApns) {
287                 menu.add(0, MENU_NEW, 0,
288                         getResources().getString(R.string.menu_new))
289                         .setIcon(R.drawable.ic_add_24dp)
290                         .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
291             }
292             menu.add(0, MENU_RESTORE, 0,
293                     getResources().getString(R.string.menu_restore))
294                     .setIcon(android.R.drawable.ic_menu_upload);
295         }
296 
297         super.onCreateOptionsMenu(menu, inflater);
298     }
299 
300     @Override
onOptionsItemSelected(MenuItem item)301     public boolean onOptionsItemSelected(MenuItem item) {
302         switch (item.getItemId()) {
303             case MENU_NEW:
304                 addNewApn();
305                 return true;
306             case MENU_RESTORE:
307                 restoreDefaultApn();
308                 return true;
309         }
310         return super.onOptionsItemSelected(item);
311     }
312 
addNewApn()313     private void addNewApn() {
314         if (Flags.newApnPageEnabled()) {
315             String route = ApnEditPageProvider.INSTANCE.getRoute(
316                     INSERT_URL, Telephony.Carriers.CONTENT_URI, mSubId);
317             SpaActivity.startSpaActivity(getContext(), route);
318         } else {
319             final Intent intent = new Intent(Intent.ACTION_INSERT, Telephony.Carriers.CONTENT_URI);
320             intent.putExtra(SUB_ID, mSubId);
321             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
322             if (!TextUtils.isEmpty(mMvnoType) && !TextUtils.isEmpty(mMvnoMatchData)) {
323                 intent.putExtra(MVNO_TYPE, mMvnoType);
324                 intent.putExtra(MVNO_MATCH_DATA, mMvnoMatchData);
325             }
326             startActivity(intent);
327         }
328     }
329 
330     @Override
onPreferenceChange(Preference preference, Object newValue)331     public boolean onPreferenceChange(Preference preference, Object newValue) {
332         Log.d(TAG, "onPreferenceChange(): Preference - " + preference
333                 + ", newValue - " + newValue + ", newValue type - "
334                 + newValue.getClass());
335         if (newValue instanceof String) {
336             mPreferredApnRepository.setPreferredApn((String) newValue);
337         }
338 
339         return true;
340     }
341 
restoreDefaultApn()342     private void restoreDefaultApn() {
343         showDialog(DIALOG_RESTORE_DEFAULTAPN);
344         mRestoreDefaultApnMode = true;
345 
346         mPreferredApnRepository.restorePreferredApn(getViewLifecycleOwner(), () -> {
347             onPreferredApnRestored();
348             return Unit.INSTANCE;
349         });
350     }
351 
onPreferredApnRestored()352     private void onPreferredApnRestored() {
353         final Activity activity = getActivity();
354         if (activity == null) {
355             mRestoreDefaultApnMode = false;
356             return;
357         }
358         fillList();
359         getPreferenceScreen().setEnabled(true);
360         mRestoreDefaultApnMode = false;
361         removeDialog(DIALOG_RESTORE_DEFAULTAPN);
362         Toast.makeText(
363                 activity,
364                 getResources().getString(R.string.restore_default_apn_completed),
365                 Toast.LENGTH_LONG).show();
366     }
367 
368     @Override
onCreateDialog(int id)369     public Dialog onCreateDialog(int id) {
370         if (id == DIALOG_RESTORE_DEFAULTAPN) {
371             final ProgressDialog dialog = new ProgressDialog(getActivity()) {
372                 public boolean onTouchEvent(MotionEvent event) {
373                     return true;
374                 }
375             };
376             dialog.setMessage(getResources().getString(R.string.restore_default_apn));
377             dialog.setCancelable(false);
378             return dialog;
379         }
380         return null;
381     }
382 
383     @Override
getDialogMetricsCategory(int dialogId)384     public int getDialogMetricsCategory(int dialogId) {
385         if (dialogId == DIALOG_RESTORE_DEFAULTAPN) {
386             return SettingsEnums.DIALOG_APN_RESTORE_DEFAULT;
387         }
388         return 0;
389     }
390 }
391