blob: 6f8826fa69fcca87a2b9b53fdd8e1431b7ca22c0 [file] [log] [blame]
Santos Cordon7d4ddf62013-07-10 11:58:08 -07001/*
2 * Copyright (C) 2006 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
17package com.android.phone;
18
Ta-wei Yenafca2d62017-07-18 17:20:59 -070019import static android.Manifest.permission.READ_PHONE_STATE;
20
Rambo Wang5d5de762022-08-10 22:54:23 +000021import android.annotation.NonNull;
chen xubaf9fe52019-07-02 17:28:24 -070022import android.annotation.Nullable;
Tyler Gunn99ae2692020-12-09 11:35:21 -080023import android.app.BroadcastOptions;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070024import android.app.Notification;
25import android.app.NotificationManager;
26import android.app.PendingIntent;
27import android.app.StatusBarManager;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070028import android.content.ComponentName;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070029import android.content.Context;
30import android.content.Intent;
31import android.content.SharedPreferences;
Jordan Liu9ddde022019-08-05 13:49:08 -070032import android.content.pm.PackageManager;
Ta-wei Yen5bb19562016-11-16 11:05:37 -080033import android.content.pm.ResolveInfo;
Nancy Chenb4a92702014-12-04 15:57:29 -080034import android.content.res.Resources;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070035import android.net.Uri;
irisykyang25202462018-11-13 14:49:54 +080036import android.os.Handler;
37import android.os.Message;
Jonathan Basseric31f1f32015-05-12 10:13:03 -070038import android.os.PersistableBundle;
irisykyang25202462018-11-13 14:49:54 +080039import android.os.SystemClock;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070040import android.os.SystemProperties;
Andrew Lee99d0ac22014-10-10 13:18:04 -070041import android.os.UserHandle;
42import android.os.UserManager;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070043import android.preference.PreferenceManager;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070044import android.provider.ContactsContract.PhoneLookup;
Jeff Davidson6d9bf522017-11-03 14:51:13 -070045import android.provider.Settings;
Tyler Gunn4d45d1c2014-09-12 22:17:53 -070046import android.telecom.PhoneAccount;
Andrew Leed5165b02014-12-05 15:53:58 -080047import android.telecom.PhoneAccountHandle;
48import android.telecom.TelecomManager;
Jonathan Basseri3649bdb2015-04-30 22:39:11 -070049import android.telephony.CarrierConfigManager;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070050import android.telephony.PhoneNumberUtils;
Rambo Wang5d5de762022-08-10 22:54:23 +000051import android.telephony.RadioAccessFamily;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070052import android.telephony.ServiceState;
Andrew Lee2fcb6c32014-12-04 14:52:35 -080053import android.telephony.SubscriptionInfo;
Andrew Leea82b8202014-11-21 16:18:28 -080054import android.telephony.SubscriptionManager;
Andrew Leed5165b02014-12-05 15:53:58 -080055import android.telephony.TelephonyManager;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070056import android.text.TextUtils;
Tyler Gunn9c1071f2014-12-09 10:07:54 -080057import android.util.ArrayMap;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070058import android.util.Log;
irisykyang25202462018-11-13 14:49:54 +080059import android.util.SparseArray;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070060import android.widget.Toast;
Ta-wei Yenb29425b2016-09-21 17:28:14 -070061
Santos Cordon7d4ddf62013-07-10 11:58:08 -070062import com.android.internal.telephony.Phone;
Jayachandran C2ef9a482017-05-12 22:07:47 -070063import com.android.internal.telephony.PhoneFactory;
Rambo Wang5d5de762022-08-10 22:54:23 +000064import com.android.internal.telephony.RILConstants;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070065import com.android.internal.telephony.TelephonyCapabilities;
fionaxu8b7620d2017-05-01 16:22:17 -070066import com.android.internal.telephony.util.NotificationChannelController;
Andrew Leebf07f762015-04-07 19:05:50 -070067import com.android.phone.settings.VoicemailSettingsActivity;
Ta-wei Yenb29425b2016-09-21 17:28:14 -070068
Meng Wang1a19e442019-10-11 10:09:00 -070069import java.util.ArrayList;
chen xubaf9fe52019-07-02 17:28:24 -070070import java.util.HashSet;
Tyler Gunn9c1071f2014-12-09 10:07:54 -080071import java.util.Iterator;
Andrew Lee99d0ac22014-10-10 13:18:04 -070072import java.util.List;
Tyler Gunn9c1071f2014-12-09 10:07:54 -080073import java.util.Set;
Andrew Lee99d0ac22014-10-10 13:18:04 -070074
Santos Cordon7d4ddf62013-07-10 11:58:08 -070075/**
76 * NotificationManager-related utility code for the Phone app.
77 *
78 * This is a singleton object which acts as the interface to the
79 * framework's NotificationManager, and is used to display status bar
80 * icons and control other status bar-related behavior.
81 *
82 * @see PhoneGlobals.notificationMgr
83 */
Chiao Cheng312b9c92013-09-16 15:40:53 -070084public class NotificationMgr {
Andrew Leea82b8202014-11-21 16:18:28 -080085 private static final String LOG_TAG = NotificationMgr.class.getSimpleName();
Santos Cordon7d4ddf62013-07-10 11:58:08 -070086 private static final boolean DBG =
87 (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
88 // Do not check in with VDBG = true, since that may write PII to the system log.
89 private static final boolean VDBG = false;
90
Ta-wei Yenb29425b2016-09-21 17:28:14 -070091 private static final String MWI_SHOULD_CHECK_VVM_CONFIGURATION_KEY_PREFIX =
92 "mwi_should_check_vvm_configuration_state_";
93
Santos Cordon7d4ddf62013-07-10 11:58:08 -070094 // notification types
Santos Cordonf68db2e2014-07-02 14:40:44 -070095 static final int MMI_NOTIFICATION = 1;
96 static final int NETWORK_SELECTION_NOTIFICATION = 2;
97 static final int VOICEMAIL_NOTIFICATION = 3;
98 static final int CALL_FORWARD_NOTIFICATION = 4;
Jordan Liuc353b162019-07-23 15:54:41 -070099 static final int DATA_ROAMING_NOTIFICATION = 5;
Santos Cordonf68db2e2014-07-02 14:40:44 -0700100 static final int SELECTED_OPERATOR_FAIL_NOTIFICATION = 6;
chen xubaf9fe52019-07-02 17:28:24 -0700101 static final int LIMITED_SIM_FUNCTION_NOTIFICATION = 7;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700102
irisykyang25202462018-11-13 14:49:54 +0800103 // Event for network selection notification.
104 private static final int EVENT_PENDING_NETWORK_SELECTION_NOTIFICATION = 1;
105
106 private static final long NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIME_IN_MS = 10000L;
107 private static final int NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIMES = 10;
108
109 private static final int STATE_UNKNOWN_SERVICE = -1;
110
chen xubaf9fe52019-07-02 17:28:24 -0700111 private static final String ACTION_MOBILE_NETWORK_LIST = "android.settings.MOBILE_NETWORK_LIST";
112
Tyler Gunn99ae2692020-12-09 11:35:21 -0800113 /**
114 * Grant recipients of new voicemail broadcasts a 10sec allowlist so they can start a background
115 * service to do VVM processing.
116 */
117 private final long VOICEMAIL_ALLOW_LIST_DURATION_MILLIS = 10000L;
118
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700119 /** The singleton NotificationMgr instance. */
120 private static NotificationMgr sInstance;
121
122 private PhoneGlobals mApp;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700123
124 private Context mContext;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700125 private StatusBarManager mStatusBarManager;
Andrew Lee99d0ac22014-10-10 13:18:04 -0700126 private UserManager mUserManager;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700127 private Toast mToast;
Andrew Lee2fcb6c32014-12-04 14:52:35 -0800128 private SubscriptionManager mSubscriptionManager;
Andrew Leed5165b02014-12-05 15:53:58 -0800129 private TelecomManager mTelecomManager;
130 private TelephonyManager mTelephonyManager;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700131
irisykyang25202462018-11-13 14:49:54 +0800132 // used to track the notification of selected network unavailable, per subscription id.
133 private SparseArray<Boolean> mSelectedUnavailableNotify = new SparseArray<>();
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700134
chen xubaf9fe52019-07-02 17:28:24 -0700135 // used to track the notification of limited sim function under dual sim, per subscription id.
136 private Set<Integer> mLimitedSimFunctionNotify = new HashSet<>();
137
Tyler Gunn9c1071f2014-12-09 10:07:54 -0800138 // used to track whether the message waiting indicator is visible, per subscription id.
139 private ArrayMap<Integer, Boolean> mMwiVisible = new ArrayMap<Integer, Boolean>();
140
irisykyang25202462018-11-13 14:49:54 +0800141 // those flags are used to track whether to show network selection notification or not.
142 private SparseArray<Integer> mPreviousServiceState = new SparseArray<>();
143 private SparseArray<Long> mOOSTimestamp = new SparseArray<>();
144 private SparseArray<Integer> mPendingEventCounter = new SparseArray<>();
145 // maps each subId to selected network operator name.
146 private SparseArray<String> mSelectedNetworkOperatorName = new SparseArray<>();
147
148 private final Handler mHandler = new Handler() {
149 @Override
150 public void handleMessage(Message msg) {
151 switch (msg.what) {
152 case EVENT_PENDING_NETWORK_SELECTION_NOTIFICATION:
153 int subId = (int) msg.obj;
154 TelephonyManager telephonyManager =
155 mTelephonyManager.createForSubscriptionId(subId);
156 if (telephonyManager.getServiceState() != null) {
157 shouldShowNotification(telephonyManager.getServiceState().getState(),
158 subId);
159 }
160 break;
161 }
162 }
163 };
164
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700165 /**
166 * Private constructor (this is a singleton).
Santos Cordonf68db2e2014-07-02 14:40:44 -0700167 * @see #init(PhoneGlobals)
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700168 */
169 private NotificationMgr(PhoneGlobals app) {
170 mApp = app;
171 mContext = app;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700172 mStatusBarManager =
173 (StatusBarManager) app.getSystemService(Context.STATUS_BAR_SERVICE);
Andrew Lee99d0ac22014-10-10 13:18:04 -0700174 mUserManager = (UserManager) app.getSystemService(Context.USER_SERVICE);
Andrew Lee2fcb6c32014-12-04 14:52:35 -0800175 mSubscriptionManager = SubscriptionManager.from(mContext);
Tyler Gunn5ddfdc92019-10-31 13:08:23 -0700176 mTelecomManager = app.getSystemService(TelecomManager.class);
Andrew Leed5165b02014-12-05 15:53:58 -0800177 mTelephonyManager = (TelephonyManager) app.getSystemService(Context.TELEPHONY_SERVICE);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700178 }
179
180 /**
181 * Initialize the singleton NotificationMgr instance.
182 *
183 * This is only done once, at startup, from PhoneApp.onCreate().
184 * From then on, the NotificationMgr instance is available via the
185 * PhoneApp's public "notificationMgr" field, which is why there's no
186 * getInstance() method here.
187 */
188 /* package */ static NotificationMgr init(PhoneGlobals app) {
189 synchronized (NotificationMgr.class) {
190 if (sInstance == null) {
191 sInstance = new NotificationMgr(app);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700192 } else {
193 Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance);
194 }
195 return sInstance;
196 }
197 }
198
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700199 /** The projection to use when querying the phones table */
200 static final String[] PHONES_PROJECTION = new String[] {
201 PhoneLookup.NUMBER,
202 PhoneLookup.DISPLAY_NAME,
203 PhoneLookup._ID
204 };
205
206 /**
Tyler Gunn9c1071f2014-12-09 10:07:54 -0800207 * Re-creates the message waiting indicator (voicemail) notification if it is showing. Used to
208 * refresh the voicemail intent on the indicator when the user changes it via the voicemail
209 * settings screen. The voicemail notification sound is suppressed.
210 *
211 * @param subId The subscription Id.
212 */
213 /* package */ void refreshMwi(int subId) {
214 // In a single-sim device, subId can be -1 which means "no sub id". In this case we will
215 // reference the single subid stored in the mMwiVisible map.
Ta-wei Yena1390d42017-12-04 15:11:33 -0800216 if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
Tyler Gunn9c1071f2014-12-09 10:07:54 -0800217 if (mMwiVisible.keySet().size() == 1) {
218 Set<Integer> keySet = mMwiVisible.keySet();
219 Iterator<Integer> keyIt = keySet.iterator();
220 if (!keyIt.hasNext()) {
221 return;
222 }
223 subId = keyIt.next();
224 }
225 }
226 if (mMwiVisible.containsKey(subId)) {
227 boolean mwiVisible = mMwiVisible.get(subId);
228 if (mwiVisible) {
Kazuya Ohshiro263737d2017-10-06 19:42:03 +0900229 mApp.notifier.updatePhoneStateListeners(true);
Tyler Gunn9c1071f2014-12-09 10:07:54 -0800230 }
231 }
232 }
233
Ta-wei Yenb29425b2016-09-21 17:28:14 -0700234 public void setShouldCheckVisualVoicemailConfigurationForMwi(int subId, boolean enabled) {
235 if (!SubscriptionManager.isValidSubscriptionId(subId)) {
236 Log.e(LOG_TAG, "setShouldCheckVisualVoicemailConfigurationForMwi: invalid subId"
237 + subId);
238 return;
239 }
240
241 PreferenceManager.getDefaultSharedPreferences(mContext).edit()
242 .putBoolean(MWI_SHOULD_CHECK_VVM_CONFIGURATION_KEY_PREFIX + subId, enabled)
243 .apply();
244 }
245
246 private boolean shouldCheckVisualVoicemailConfigurationForMwi(int subId) {
247 if (!SubscriptionManager.isValidSubscriptionId(subId)) {
248 Log.e(LOG_TAG, "shouldCheckVisualVoicemailConfigurationForMwi: invalid subId" + subId);
249 return true;
250 }
251 return PreferenceManager
252 .getDefaultSharedPreferences(mContext)
253 .getBoolean(MWI_SHOULD_CHECK_VVM_CONFIGURATION_KEY_PREFIX + subId, true);
254 }
Tyler Gunn9c1071f2014-12-09 10:07:54 -0800255 /**
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700256 * Updates the message waiting indicator (voicemail) notification.
257 *
258 * @param visible true if there are messages waiting
259 */
Andrew Lee2fcb6c32014-12-04 14:52:35 -0800260 /* package */ void updateMwi(int subId, boolean visible) {
Ta-wei Yen282a9702017-05-30 17:32:29 -0700261 updateMwi(subId, visible, false /* isRefresh */);
Tyler Gunn9c1071f2014-12-09 10:07:54 -0800262 }
263
264 /**
265 * Updates the message waiting indicator (voicemail) notification.
266 *
267 * @param subId the subId to update.
268 * @param visible true if there are messages waiting
Ta-wei Yen282a9702017-05-30 17:32:29 -0700269 * @param isRefresh {@code true} if the notification is a refresh and the user should not be
270 * notified again.
Tyler Gunn9c1071f2014-12-09 10:07:54 -0800271 */
Ta-wei Yen282a9702017-05-30 17:32:29 -0700272 void updateMwi(int subId, boolean visible, boolean isRefresh) {
Andrew Leea82b8202014-11-21 16:18:28 -0800273 if (!PhoneGlobals.sVoiceCapable) {
274 // Do not show the message waiting indicator on devices which are not voice capable.
275 // These events *should* be blocked at the telephony layer for such devices.
276 Log.w(LOG_TAG, "Called updateMwi() on non-voice-capable device! Ignoring...");
277 return;
278 }
279
Nancy Chen2cf7f292015-05-15 11:00:10 -0700280 Phone phone = PhoneGlobals.getPhone(subId);
Yorke Lee67a62a22014-12-15 18:46:17 -0800281 Log.i(LOG_TAG, "updateMwi(): subId " + subId + " update to " + visible);
Andrew Leef8ad78f2014-12-15 16:17:29 -0800282 mMwiVisible.put(subId, visible);
283
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700284 if (visible) {
Andrew Lee2fcb6c32014-12-04 14:52:35 -0800285 if (phone == null) {
Andrew Leed5165b02014-12-05 15:53:58 -0800286 Log.w(LOG_TAG, "Found null phone for: " + subId);
287 return;
288 }
289
290 SubscriptionInfo subInfo = mSubscriptionManager.getActiveSubscriptionInfo(subId);
291 if (subInfo == null) {
292 Log.w(LOG_TAG, "Found null subscription info for: " + subId);
Andrew Lee2fcb6c32014-12-04 14:52:35 -0800293 return;
294 }
295
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700296 int resId = android.R.drawable.stat_notify_voicemail;
Kazuya Ohshiro263737d2017-10-06 19:42:03 +0900297 if (mTelephonyManager.getPhoneCount() > 1) {
298 resId = (phone.getPhoneId() == 0) ? R.drawable.stat_notify_voicemail_sub1
299 : R.drawable.stat_notify_voicemail_sub2;
300 }
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700301
302 // This Notification can get a lot fancier once we have more
303 // information about the current voicemail messages.
304 // (For example, the current voicemail system can't tell
305 // us the caller-id or timestamp of a message, or tell us the
306 // message count.)
307
308 // But for now, the UI is ultra-simple: if the MWI indication
309 // is supposed to be visible, just show a single generic
310 // notification.
311
312 String notificationTitle = mContext.getString(R.string.notification_voicemail_title);
Andrew Lee2fcb6c32014-12-04 14:52:35 -0800313 String vmNumber = phone.getVoiceMailNumber();
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700314 if (DBG) log("- got vm number: '" + vmNumber + "'");
315
Andrew Leea82b8202014-11-21 16:18:28 -0800316 // The voicemail number may be null because:
317 // (1) This phone has no voicemail number.
318 // (2) This phone has a voicemail number, but the SIM isn't ready yet. This may
319 // happen when the device first boots if we get a MWI notification when we
320 // register on the network before the SIM has loaded. In this case, the
Andrew Lee2fcb6c32014-12-04 14:52:35 -0800321 // SubscriptionListener in CallNotifier will update this once the SIM is loaded.
322 if ((vmNumber == null) && !phone.getIccRecordsLoaded()) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700323 if (DBG) log("- Null vm number: SIM records not loaded (yet)...");
Andrew Leea82b8202014-11-21 16:18:28 -0800324 return;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700325 }
326
Bryce Lee5dc90842015-08-11 07:57:14 -0700327 Integer vmCount = null;
328
Andrew Lee2fcb6c32014-12-04 14:52:35 -0800329 if (TelephonyCapabilities.supportsVoiceMessageCount(phone)) {
Bryce Lee5dc90842015-08-11 07:57:14 -0700330 vmCount = phone.getVoiceMessageCount();
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700331 String titleFormat = mContext.getString(R.string.notification_voicemail_title_count);
332 notificationTitle = String.format(titleFormat, vmCount);
333 }
334
Tyler Gunn9c1071f2014-12-09 10:07:54 -0800335 // This pathway only applies to PSTN accounts; only SIMS have subscription ids.
336 PhoneAccountHandle phoneAccountHandle = PhoneUtils.makePstnPhoneAccountHandle(phone);
337
338 Intent intent;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700339 String notificationText;
Bryce Lee5dc90842015-08-11 07:57:14 -0700340 boolean isSettingsIntent = TextUtils.isEmpty(vmNumber);
341
342 if (isSettingsIntent) {
Tyler Gunn9c1071f2014-12-09 10:07:54 -0800343 notificationText = mContext.getString(
344 R.string.notification_voicemail_no_vm_number);
345
346 // If the voicemail number if unknown, instead of calling voicemail, take the user
347 // to the voicemail settings.
348 notificationText = mContext.getString(
349 R.string.notification_voicemail_no_vm_number);
Andrew Leebf07f762015-04-07 19:05:50 -0700350 intent = new Intent(VoicemailSettingsActivity.ACTION_ADD_VOICEMAIL);
Tyler Gunn9c1071f2014-12-09 10:07:54 -0800351 intent.putExtra(SubscriptionInfoHelper.SUB_ID_EXTRA, subId);
Andrew Leebf07f762015-04-07 19:05:50 -0700352 intent.setClass(mContext, VoicemailSettingsActivity.class);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700353 } else {
Tyler Gunn9c1071f2014-12-09 10:07:54 -0800354 if (mTelephonyManager.getPhoneCount() > 1) {
355 notificationText = subInfo.getDisplayName().toString();
Andrew Leed5165b02014-12-05 15:53:58 -0800356 } else {
357 notificationText = String.format(
358 mContext.getString(R.string.notification_voicemail_text_format),
359 PhoneNumberUtils.formatNumber(vmNumber));
360 }
Tyler Gunn9c1071f2014-12-09 10:07:54 -0800361 intent = new Intent(
362 Intent.ACTION_CALL, Uri.fromParts(PhoneAccount.SCHEME_VOICEMAIL, "",
Jonathan Basseri3649bdb2015-04-30 22:39:11 -0700363 null));
Tyler Gunn9c1071f2014-12-09 10:07:54 -0800364 intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700365 }
366
Andrew Lee2fcb6c32014-12-04 14:52:35 -0800367 PendingIntent pendingIntent =
Shuo Qian9a41a172020-04-24 19:19:31 -0700368 PendingIntent.getActivity(mContext, subId /* requestCode */, intent,
369 PendingIntent.FLAG_IMMUTABLE);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700370
Nancy Chenb4a92702014-12-04 15:57:29 -0800371 Resources res = mContext.getResources();
Jonathan Basseric31f1f32015-05-12 10:13:03 -0700372 PersistableBundle carrierConfig = PhoneGlobals.getInstance().getCarrierConfigForSubId(
Ta-wei Yen9b37a872016-05-27 12:16:58 -0700373 subId);
fionaxu8b7620d2017-05-01 16:22:17 -0700374 Notification.Builder builder = new Notification.Builder(mContext);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700375 builder.setSmallIcon(resId)
376 .setWhen(System.currentTimeMillis())
Andrew Leed5165b02014-12-05 15:53:58 -0800377 .setColor(subInfo.getIconTint())
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700378 .setContentTitle(notificationTitle)
379 .setContentText(notificationText)
380 .setContentIntent(pendingIntent)
Nancy Chenb4a92702014-12-04 15:57:29 -0800381 .setColor(res.getColor(R.color.dialer_theme_color))
Jonathan Basseri3649bdb2015-04-30 22:39:11 -0700382 .setOngoing(carrierConfig.getBoolean(
fionaxu75b66a72017-04-19 19:01:56 -0700383 CarrierConfigManager.KEY_VOICEMAIL_NOTIFICATION_PERSISTENT_BOOL))
Jordan Liu575c0d02019-07-09 16:29:48 -0700384 .setChannelId(NotificationChannelController.CHANNEL_ID_VOICE_MAIL)
Ta-wei Yen282a9702017-05-30 17:32:29 -0700385 .setOnlyAlertOnce(isRefresh);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700386
Andrew Lee99d0ac22014-10-10 13:18:04 -0700387 final Notification notification = builder.build();
Meng Wang1a19e442019-10-11 10:09:00 -0700388 List<UserHandle> users = getUsersExcludeDying();
389 for (UserHandle userHandle : users) {
Amit Mahajan2f489b72019-10-08 11:21:52 -0700390 if (!hasUserRestriction(
Yorke Lee047b1f92014-10-24 10:22:41 -0700391 UserManager.DISALLOW_OUTGOING_CALLS, userHandle)
Jordan Liua55aaaa2019-08-28 15:33:05 -0700392 && !mUserManager.isManagedProfile(userHandle.getIdentifier())) {
Ta-wei Yena71a38b2017-02-24 18:19:27 -0800393 if (!maybeSendVoicemailNotificationUsingDefaultDialer(phone, vmCount, vmNumber,
Ta-wei Yen282a9702017-05-30 17:32:29 -0700394 pendingIntent, isSettingsIntent, userHandle, isRefresh)) {
Jordan Liu9ddde022019-08-05 13:49:08 -0700395 notifyAsUser(
Bryce Lee5dc90842015-08-11 07:57:14 -0700396 Integer.toString(subId) /* tag */,
397 VOICEMAIL_NOTIFICATION,
398 notification,
399 userHandle);
400 }
Andrew Lee99d0ac22014-10-10 13:18:04 -0700401 }
402 }
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700403 } else {
Meng Wang1a19e442019-10-11 10:09:00 -0700404 List<UserHandle> users = getUsersExcludeDying();
405 for (UserHandle userHandle : users) {
Amit Mahajan2f489b72019-10-08 11:21:52 -0700406 if (!hasUserRestriction(
Ta-wei Yen5bb19562016-11-16 11:05:37 -0800407 UserManager.DISALLOW_OUTGOING_CALLS, userHandle)
Jordan Liua55aaaa2019-08-28 15:33:05 -0700408 && !mUserManager.isManagedProfile(userHandle.getIdentifier())) {
Ta-wei Yena71a38b2017-02-24 18:19:27 -0800409 if (!maybeSendVoicemailNotificationUsingDefaultDialer(phone, 0, null, null,
Ta-wei Yen282a9702017-05-30 17:32:29 -0700410 false, userHandle, isRefresh)) {
Jordan Liu9ddde022019-08-05 13:49:08 -0700411 cancelAsUser(
Ta-wei Yen5bb19562016-11-16 11:05:37 -0800412 Integer.toString(subId) /* tag */,
413 VOICEMAIL_NOTIFICATION,
414 userHandle);
415 }
416 }
Bryce Lee5dc90842015-08-11 07:57:14 -0700417 }
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700418 }
419 }
420
Meng Wang1a19e442019-10-11 10:09:00 -0700421 private List<UserHandle> getUsersExcludeDying() {
422 long[] serialNumbersOfUsers =
423 mUserManager.getSerialNumbersOfUsers(/* excludeDying= */ true);
424 List<UserHandle> users = new ArrayList<>(serialNumbersOfUsers.length);
425 for (long serialNumber : serialNumbersOfUsers) {
426 users.add(mUserManager.getUserForSerialNumber(serialNumber));
427 }
428 return users;
429 }
430
Amit Mahajan2f489b72019-10-08 11:21:52 -0700431 private boolean hasUserRestriction(String restrictionKey, UserHandle userHandle) {
432 final List<UserManager.EnforcingUser> sources = mUserManager
433 .getUserRestrictionSources(restrictionKey, userHandle);
434 return (sources != null && !sources.isEmpty());
435 }
436
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700437 /**
Ta-wei Yen5bb19562016-11-16 11:05:37 -0800438 * Sends a broadcast with the voicemail notification information to the default dialer. This
439 * method is also used to indicate to the default dialer when to clear the
440 * notification. A pending intent can be passed to the default dialer to indicate an action to
Bryce Lee5dc90842015-08-11 07:57:14 -0700441 * be taken as it would by a notification produced in this class.
Ta-wei Yena71a38b2017-02-24 18:19:27 -0800442 * @param phone The phone the notification is sent from
Bryce Lee5dc90842015-08-11 07:57:14 -0700443 * @param count The number of pending voicemail messages to indicate on the notification. A
444 * Value of 0 is passed here to indicate that the notification should be cleared.
445 * @param number The voicemail phone number if specified.
446 * @param pendingIntent The intent that should be passed as the action to be taken.
447 * @param isSettingsIntent {@code true} to indicate the pending intent is to launch settings.
448 * otherwise, {@code false} to indicate the intent launches voicemail.
Ta-wei Yen5bb19562016-11-16 11:05:37 -0800449 * @param userHandle The user to receive the notification. Each user can have their own default
450 * dialer.
451 * @return {@code true} if the default was notified of the notification.
Bryce Lee5dc90842015-08-11 07:57:14 -0700452 */
Ta-wei Yena71a38b2017-02-24 18:19:27 -0800453 private boolean maybeSendVoicemailNotificationUsingDefaultDialer(Phone phone, Integer count,
454 String number, PendingIntent pendingIntent, boolean isSettingsIntent,
Ta-wei Yen282a9702017-05-30 17:32:29 -0700455 UserHandle userHandle, boolean isRefresh) {
Ta-wei Yen5bb19562016-11-16 11:05:37 -0800456
457 if (shouldManageNotificationThroughDefaultDialer(userHandle)) {
458 Intent intent = getShowVoicemailIntentForDefaultDialer(userHandle);
Bryce Lee5dc90842015-08-11 07:57:14 -0700459 intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
Bryce Lee5dc90842015-08-11 07:57:14 -0700460 intent.setAction(TelephonyManager.ACTION_SHOW_VOICEMAIL_NOTIFICATION);
Ta-wei Yena71a38b2017-02-24 18:19:27 -0800461 intent.putExtra(TelephonyManager.EXTRA_PHONE_ACCOUNT_HANDLE,
462 PhoneUtils.makePstnPhoneAccountHandle(phone));
Ta-wei Yenafca2d62017-07-18 17:20:59 -0700463 intent.putExtra(TelephonyManager.EXTRA_IS_REFRESH, isRefresh);
Bryce Lee5dc90842015-08-11 07:57:14 -0700464 if (count != null) {
465 intent.putExtra(TelephonyManager.EXTRA_NOTIFICATION_COUNT, count);
466 }
467
468 // Additional information about the voicemail notification beyond the count is only
469 // present when the count not specified or greater than 0. The value of 0 represents
470 // clearing the notification, which does not require additional information.
471 if (count == null || count > 0) {
472 if (!TextUtils.isEmpty(number)) {
473 intent.putExtra(TelephonyManager.EXTRA_VOICEMAIL_NUMBER, number);
474 }
475
476 if (pendingIntent != null) {
477 intent.putExtra(isSettingsIntent
478 ? TelephonyManager.EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT
479 : TelephonyManager.EXTRA_CALL_VOICEMAIL_INTENT,
480 pendingIntent);
481 }
482 }
Tyler Gunn99ae2692020-12-09 11:35:21 -0800483
484 BroadcastOptions bopts = BroadcastOptions.makeBasic();
485 bopts.setTemporaryAppWhitelistDuration(VOICEMAIL_ALLOW_LIST_DURATION_MILLIS);
486 mContext.sendBroadcastAsUser(intent, userHandle, READ_PHONE_STATE, bopts.toBundle());
Bryce Lee5dc90842015-08-11 07:57:14 -0700487 return true;
488 }
489
490 return false;
491 }
492
Ta-wei Yen5bb19562016-11-16 11:05:37 -0800493 private Intent getShowVoicemailIntentForDefaultDialer(UserHandle userHandle) {
Tyler Gunn5ddfdc92019-10-31 13:08:23 -0700494 String dialerPackage = mContext.getSystemService(TelecomManager.class)
Tyler Gunn83adc052020-01-28 09:12:21 -0800495 .getDefaultDialerPackage(userHandle);
Ta-wei Yen5bb19562016-11-16 11:05:37 -0800496 return new Intent(TelephonyManager.ACTION_SHOW_VOICEMAIL_NOTIFICATION)
497 .setPackage(dialerPackage);
498 }
499
500 private boolean shouldManageNotificationThroughDefaultDialer(UserHandle userHandle) {
501 Intent intent = getShowVoicemailIntentForDefaultDialer(userHandle);
502 if (intent == null) {
503 return false;
504 }
505
506 List<ResolveInfo> receivers = mContext.getPackageManager()
507 .queryBroadcastReceivers(intent, 0);
508 return receivers.size() > 0;
509 }
510
Bryce Lee5dc90842015-08-11 07:57:14 -0700511 /**
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700512 * Updates the message call forwarding indicator notification.
513 *
Srikanth Chintala4baf0b92017-11-14 15:52:47 +0530514 * @param visible true if call forwarding enabled
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700515 */
Srikanth Chintala4baf0b92017-11-14 15:52:47 +0530516
517 /* package */ void updateCfi(int subId, boolean visible) {
518 updateCfi(subId, visible, false /* isRefresh */);
519 }
520
521 /**
522 * Updates the message call forwarding indicator notification.
523 *
524 * @param visible true if call forwarding enabled
525 */
526 /* package */ void updateCfi(int subId, boolean visible, boolean isRefresh) {
Tyler Gunna584e2c2017-09-19 11:40:12 -0700527 logi("updateCfi: subId= " + subId + ", visible=" + (visible ? "Y" : "N"));
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700528 if (visible) {
529 // If Unconditional Call Forwarding (forward all calls) for VOICE
530 // is enabled, just show a notification. We'll default to expanded
531 // view for now, so the there is less confusion about the icon. If
532 // it is deemed too weird to have CF indications as expanded views,
533 // then we'll flip the flag back.
534
535 // TODO: We may want to take a look to see if the notification can
536 // display the target to forward calls to. This will require some
537 // effort though, since there are multiple layers of messages that
538 // will need to propagate that information.
539
Andrew Leed5165b02014-12-05 15:53:58 -0800540 SubscriptionInfo subInfo = mSubscriptionManager.getActiveSubscriptionInfo(subId);
541 if (subInfo == null) {
542 Log.w(LOG_TAG, "Found null subscription info for: " + subId);
543 return;
544 }
545
546 String notificationTitle;
Kazuya Ohshiro263737d2017-10-06 19:42:03 +0900547 int resId = R.drawable.stat_sys_phone_call_forward;
Andrew Leed5165b02014-12-05 15:53:58 -0800548 if (mTelephonyManager.getPhoneCount() > 1) {
Kazuya Ohshiro263737d2017-10-06 19:42:03 +0900549 int slotId = SubscriptionManager.getSlotIndex(subId);
550 resId = (slotId == 0) ? R.drawable.stat_sys_phone_call_forward_sub1
551 : R.drawable.stat_sys_phone_call_forward_sub2;
Sarah Chin2c5f23d2020-11-02 21:52:11 -0800552 if (subInfo.getDisplayName() != null) {
553 notificationTitle = subInfo.getDisplayName().toString();
554 } else {
555 notificationTitle = mContext.getString(R.string.labelCF);
556 }
Andrew Leed5165b02014-12-05 15:53:58 -0800557 } else {
558 notificationTitle = mContext.getString(R.string.labelCF);
559 }
560
fionaxu8b7620d2017-05-01 16:22:17 -0700561 Notification.Builder builder = new Notification.Builder(mContext)
Kazuya Ohshiro263737d2017-10-06 19:42:03 +0900562 .setSmallIcon(resId)
Andrew Leed5165b02014-12-05 15:53:58 -0800563 .setColor(subInfo.getIconTint())
564 .setContentTitle(notificationTitle)
Andrew Lee99d0ac22014-10-10 13:18:04 -0700565 .setContentText(mContext.getString(R.string.sum_cfu_enabled_indicator))
566 .setShowWhen(false)
fionaxu75b66a72017-04-19 19:01:56 -0700567 .setOngoing(true)
Jordan Liu575c0d02019-07-09 16:29:48 -0700568 .setChannelId(NotificationChannelController.CHANNEL_ID_CALL_FORWARD)
Srikanth Chintala4baf0b92017-11-14 15:52:47 +0530569 .setOnlyAlertOnce(isRefresh);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700570
Tyler Gunn5bc21712021-04-30 15:07:59 -0700571 Intent intent = new Intent(TelecomManager.ACTION_SHOW_CALL_SETTINGS);
Andrew Lee2fcb6c32014-12-04 14:52:35 -0800572 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
Andrew Lee2fcb6c32014-12-04 14:52:35 -0800573 SubscriptionInfoHelper.addExtrasToIntent(
574 intent, mSubscriptionManager.getActiveSubscriptionInfo(subId));
fionaxu96ceebd2017-08-24 12:12:32 -0700575 builder.setContentIntent(PendingIntent.getActivity(mContext, subId /* requestCode */,
Shuo Qian9a41a172020-04-24 19:19:31 -0700576 intent, PendingIntent.FLAG_IMMUTABLE));
Jordan Liu9ddde022019-08-05 13:49:08 -0700577 notifyAsUser(
fionaxu96ceebd2017-08-24 12:12:32 -0700578 Integer.toString(subId) /* tag */,
579 CALL_FORWARD_NOTIFICATION,
580 builder.build(),
581 UserHandle.ALL);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700582 } else {
Meng Wang21487ab2019-11-21 10:44:42 -0800583 List<UserHandle> users = getUsersExcludeDying();
584 for (UserHandle user : users) {
585 if (mUserManager.isManagedProfile(user.getIdentifier())) {
Kazuya Ohshiro263737d2017-10-06 19:42:03 +0900586 continue;
587 }
Jordan Liu9ddde022019-08-05 13:49:08 -0700588 cancelAsUser(
Kazuya Ohshiro263737d2017-10-06 19:42:03 +0900589 Integer.toString(subId) /* tag */,
590 CALL_FORWARD_NOTIFICATION,
Meng Wang21487ab2019-11-21 10:44:42 -0800591 user);
Kazuya Ohshiro263737d2017-10-06 19:42:03 +0900592 }
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700593 }
594 }
595
596 /**
Jordan Liuc353b162019-07-23 15:54:41 -0700597 * Shows either:
598 * 1) the "Data roaming is on" notification, which
599 * appears when you're roaming and you have the "data roaming" feature turned on for the
600 * given {@code subId}.
601 * or
602 * 2) the "data disconnected due to roaming" notification, which
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700603 * appears when you lose data connectivity because you're roaming and
Pengquan Meng8783d932017-10-16 14:57:40 -0700604 * you have the "data roaming" feature turned off for the given {@code subId}.
Jordan Liuc353b162019-07-23 15:54:41 -0700605 * @param subId which subscription it's notifying about.
606 * @param roamingOn whether currently roaming is on or off. If true, we show notification
607 * 1) above; else we show notification 2).
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700608 */
Jordan Liuc353b162019-07-23 15:54:41 -0700609 /* package */ void showDataRoamingNotification(int subId, boolean roamingOn) {
610 if (DBG) {
611 log("showDataRoamingNotification() roaming " + (roamingOn ? "on" : "off")
612 + " on subId " + subId);
613 }
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700614
615 // "Mobile network settings" screen / dialog
Nazanin Bakhshi682c77d2019-05-02 17:22:27 -0700616 Intent intent = new Intent(Settings.ACTION_DATA_ROAMING_SETTINGS);
Jeff Davidson6d9bf522017-11-03 14:51:13 -0700617 intent.putExtra(Settings.EXTRA_SUB_ID, subId);
Shuo Qian9a41a172020-04-24 19:19:31 -0700618 PendingIntent contentIntent = PendingIntent.getActivity(
619 mContext, subId, intent, PendingIntent.FLAG_IMMUTABLE);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700620
Jordan Liuc353b162019-07-23 15:54:41 -0700621 CharSequence contentTitle = mContext.getText(roamingOn
622 ? R.string.roaming_on_notification_title
623 : R.string.roaming_notification_title);
624 CharSequence contentText = mContext.getText(roamingOn
625 ? R.string.roaming_enabled_message
626 : R.string.roaming_reenable_message);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700627
fionaxu8b7620d2017-05-01 16:22:17 -0700628 final Notification.Builder builder = new Notification.Builder(mContext)
Andrew Lee99d0ac22014-10-10 13:18:04 -0700629 .setSmallIcon(android.R.drawable.stat_sys_warning)
Jordan Liuc353b162019-07-23 15:54:41 -0700630 .setContentTitle(contentTitle)
Andrew Lee99d0ac22014-10-10 13:18:04 -0700631 .setColor(mContext.getResources().getColor(R.color.dialer_theme_color))
fionaxu75b66a72017-04-19 19:01:56 -0700632 .setContentText(contentText)
Jordan Liu575c0d02019-07-09 16:29:48 -0700633 .setChannelId(NotificationChannelController.CHANNEL_ID_MOBILE_DATA_STATUS)
fionaxu96ceebd2017-08-24 12:12:32 -0700634 .setContentIntent(contentIntent);
635 final Notification notif =
636 new Notification.BigTextStyle(builder).bigText(contentText).build();
Jordan Liu9ddde022019-08-05 13:49:08 -0700637 notifyAsUser(null /* tag */, DATA_ROAMING_NOTIFICATION, notif, UserHandle.ALL);
638 }
639
640 private void notifyAsUser(String tag, int id, Notification notification, UserHandle user) {
641 try {
642 Context contextForUser =
643 mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user);
644 NotificationManager notificationManager =
645 (NotificationManager) contextForUser.getSystemService(
646 Context.NOTIFICATION_SERVICE);
647 notificationManager.notify(tag, id, notification);
648 } catch (PackageManager.NameNotFoundException e) {
649 Log.e(LOG_TAG, "unable to notify for user " + user);
650 e.printStackTrace();
651 }
652 }
653
654 private void cancelAsUser(String tag, int id, UserHandle user) {
655 try {
656 Context contextForUser =
657 mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user);
658 NotificationManager notificationManager =
659 (NotificationManager) contextForUser.getSystemService(
660 Context.NOTIFICATION_SERVICE);
661 notificationManager.cancel(tag, id);
662 } catch (PackageManager.NameNotFoundException e) {
663 Log.e(LOG_TAG, "unable to cancel for user " + user);
664 e.printStackTrace();
665 }
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700666 }
667
668 /**
Jordan Liuc353b162019-07-23 15:54:41 -0700669 * Turns off the "data disconnected due to roaming" or "Data roaming is on" notification.
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700670 */
Jordan Liuc353b162019-07-23 15:54:41 -0700671 /* package */ void hideDataRoamingNotification() {
672 if (DBG) log("hideDataRoamingNotification()...");
Jordan Liu9ddde022019-08-05 13:49:08 -0700673 cancelAsUser(null, DATA_ROAMING_NOTIFICATION, UserHandle.ALL);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700674 }
675
676 /**
chen xubaf9fe52019-07-02 17:28:24 -0700677 * Shows the "Limited SIM functionality" warning notification, which appears when using a
678 * special carrier under dual sim. limited function applies for DSDS in general when two SIM
679 * cards share a single radio, thus the voice & data maybe impaired under certain scenarios.
680 */
681 public void showLimitedSimFunctionWarningNotification(int subId, @Nullable String carrierName) {
682 if (DBG) log("showLimitedSimFunctionWarningNotification carrier: " + carrierName
683 + " subId: " + subId);
684 if (mLimitedSimFunctionNotify.contains(subId)) {
685 // handle the case that user swipe the notification but condition triggers
686 // frequently which cause the same notification consistently displayed.
687 if (DBG) log("showLimitedSimFunctionWarningNotification, "
688 + "not display again if already displayed");
689 return;
690 }
691 // Navigate to "Network Selection Settings" which list all subscriptions.
692 PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0,
Shuo Qian9a41a172020-04-24 19:19:31 -0700693 new Intent(ACTION_MOBILE_NETWORK_LIST), PendingIntent.FLAG_IMMUTABLE);
Chen Xue1bbfd22019-09-18 17:17:31 -0700694 // Display phone number from the other sub
695 String line1Num = null;
696 SubscriptionManager subMgr = (SubscriptionManager) mContext.getSystemService(
697 Context.TELEPHONY_SUBSCRIPTION_SERVICE);
698 List<SubscriptionInfo> subList = subMgr.getActiveSubscriptionInfoList(false);
699 for (SubscriptionInfo sub : subList) {
700 if (sub.getSubscriptionId() != subId) {
701 line1Num = mTelephonyManager.getLine1Number(sub.getSubscriptionId());
702 }
703 }
chen xubaf9fe52019-07-02 17:28:24 -0700704 final CharSequence contentText = TextUtils.isEmpty(line1Num) ?
705 String.format(mContext.getText(
Chen Xu251ce8f2019-09-13 20:24:22 -0700706 R.string.limited_sim_function_notification_message).toString(), carrierName) :
chen xubaf9fe52019-07-02 17:28:24 -0700707 String.format(mContext.getText(
708 R.string.limited_sim_function_with_phone_num_notification_message).toString(),
Chen Xu251ce8f2019-09-13 20:24:22 -0700709 carrierName, line1Num);
chen xubaf9fe52019-07-02 17:28:24 -0700710 final Notification.Builder builder = new Notification.Builder(mContext)
711 .setSmallIcon(R.drawable.ic_sim_card)
712 .setContentTitle(mContext.getText(
713 R.string.limited_sim_function_notification_title))
714 .setContentText(contentText)
715 .setOnlyAlertOnce(true)
716 .setOngoing(true)
Jordan Liua55aaaa2019-08-28 15:33:05 -0700717 .setChannelId(NotificationChannelController.CHANNEL_ID_SIM_HIGH_PRIORITY)
chen xubaf9fe52019-07-02 17:28:24 -0700718 .setContentIntent(contentIntent);
719 final Notification notification = new Notification.BigTextStyle(builder).bigText(
720 contentText).build();
721
Jordan Liu9ddde022019-08-05 13:49:08 -0700722 notifyAsUser(Integer.toString(subId),
chen xubaf9fe52019-07-02 17:28:24 -0700723 LIMITED_SIM_FUNCTION_NOTIFICATION,
724 notification, UserHandle.ALL);
725 mLimitedSimFunctionNotify.add(subId);
726 }
727
728 /**
729 * Dismiss the "Limited SIM functionality" warning notification for the given subId.
730 */
731 public void dismissLimitedSimFunctionWarningNotification(int subId) {
732 if (DBG) log("dismissLimitedSimFunctionWarningNotification subId: " + subId);
733 if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
734 // dismiss all notifications
735 for (int id : mLimitedSimFunctionNotify) {
Jordan Liu9ddde022019-08-05 13:49:08 -0700736 cancelAsUser(Integer.toString(id),
chen xubaf9fe52019-07-02 17:28:24 -0700737 LIMITED_SIM_FUNCTION_NOTIFICATION, UserHandle.ALL);
738 }
739 mLimitedSimFunctionNotify.clear();
740 } else if (mLimitedSimFunctionNotify.contains(subId)) {
Jordan Liu9ddde022019-08-05 13:49:08 -0700741 cancelAsUser(Integer.toString(subId),
chen xubaf9fe52019-07-02 17:28:24 -0700742 LIMITED_SIM_FUNCTION_NOTIFICATION, UserHandle.ALL);
743 mLimitedSimFunctionNotify.remove(subId);
744 }
745 }
746
747 /**
748 * Dismiss the "Limited SIM functionality" warning notification for all inactive subscriptions.
749 */
750 public void dismissLimitedSimFunctionWarningNotificationForInactiveSubs() {
751 if (DBG) log("dismissLimitedSimFunctionWarningNotificationForInactiveSubs");
752 // dismiss notification for inactive subscriptions.
753 // handle the corner case that SIM change by SIM refresh doesn't clear the notification
754 // from the old SIM if both old & new SIM configured to display the notification.
755 mLimitedSimFunctionNotify.removeIf(id -> {
756 if (!mSubscriptionManager.isActiveSubId(id)) {
Jordan Liu9ddde022019-08-05 13:49:08 -0700757 cancelAsUser(Integer.toString(id),
chen xubaf9fe52019-07-02 17:28:24 -0700758 LIMITED_SIM_FUNCTION_NOTIFICATION, UserHandle.ALL);
759 return true;
760 }
761 return false;
762 });
763 }
764
765 /**
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700766 * Display the network selection "no service" notification
767 * @param operator is the numeric operator number
Jayachandran C2ef9a482017-05-12 22:07:47 -0700768 * @param subId is the subscription ID
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700769 */
Jayachandran C2ef9a482017-05-12 22:07:47 -0700770 private void showNetworkSelection(String operator, int subId) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700771 if (DBG) log("showNetworkSelection(" + operator + ")...");
772
Youming Ye0509b532018-09-14 16:21:17 -0700773 if (!TextUtils.isEmpty(operator)) {
774 operator = String.format(" (%s)", operator);
775 }
fionaxu8b7620d2017-05-01 16:22:17 -0700776 Notification.Builder builder = new Notification.Builder(mContext)
Andrew Lee99d0ac22014-10-10 13:18:04 -0700777 .setSmallIcon(android.R.drawable.stat_sys_warning)
778 .setContentTitle(mContext.getString(R.string.notification_network_selection_title))
779 .setContentText(
780 mContext.getString(R.string.notification_network_selection_text, operator))
781 .setShowWhen(false)
fionaxu75b66a72017-04-19 19:01:56 -0700782 .setOngoing(true)
Jordan Liu575c0d02019-07-09 16:29:48 -0700783 .setChannelId(NotificationChannelController.CHANNEL_ID_ALERT);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700784
785 // create the target network operators settings intent
786 Intent intent = new Intent(Intent.ACTION_MAIN);
787 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
788 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
Malcolm Chen34d4fa52017-06-05 19:02:16 -0700789 // Use MobileNetworkSettings to handle the selection intent
Wei Liube964582015-08-21 11:57:00 -0700790 intent.setComponent(new ComponentName(
Malcolm Chen34d4fa52017-06-05 19:02:16 -0700791 mContext.getString(R.string.mobile_network_settings_package),
792 mContext.getString(R.string.mobile_network_settings_class)));
Pengquan Meng984ba422019-09-05 18:00:06 -0700793 intent.putExtra(Settings.EXTRA_SUB_ID, subId);
Shuo Qian9a41a172020-04-24 19:19:31 -0700794 builder.setContentIntent(
795 PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE));
Jordan Liu9ddde022019-08-05 13:49:08 -0700796 notifyAsUser(
irisykyang25202462018-11-13 14:49:54 +0800797 Integer.toString(subId) /* tag */,
fionaxu96ceebd2017-08-24 12:12:32 -0700798 SELECTED_OPERATOR_FAIL_NOTIFICATION,
799 builder.build(),
800 UserHandle.ALL);
irisykyang25202462018-11-13 14:49:54 +0800801 mSelectedUnavailableNotify.put(subId, true);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700802 }
803
804 /**
805 * Turn off the network selection "no service" notification
806 */
irisykyang25202462018-11-13 14:49:54 +0800807 private void cancelNetworkSelection(int subId) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700808 if (DBG) log("cancelNetworkSelection()...");
Jordan Liu9ddde022019-08-05 13:49:08 -0700809 cancelAsUser(
irisykyang25202462018-11-13 14:49:54 +0800810 Integer.toString(subId) /* tag */, SELECTED_OPERATOR_FAIL_NOTIFICATION,
811 UserHandle.ALL);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700812 }
813
814 /**
815 * Update notification about no service of user selected operator
816 *
817 * @param serviceState Phone service state
Jayachandran C2ef9a482017-05-12 22:07:47 -0700818 * @param subId The subscription ID
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700819 */
Jayachandran C2ef9a482017-05-12 22:07:47 -0700820 void updateNetworkSelection(int serviceState, int subId) {
821 int phoneId = SubscriptionManager.getPhoneId(subId);
822 Phone phone = SubscriptionManager.isValidPhoneId(phoneId) ?
823 PhoneFactory.getPhone(phoneId) : PhoneFactory.getDefaultPhone();
824 if (TelephonyCapabilities.supportsNetworkSelection(phone)) {
Amit Mahajana60be872015-01-15 16:05:08 -0800825 if (SubscriptionManager.isValidSubscriptionId(subId)) {
fionaxu996a1c32018-04-13 15:00:37 -0700826 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
827 String selectedNetworkOperatorName =
828 sp.getString(Phone.NETWORK_SELECTION_NAME_KEY + subId, "");
829 // get the shared preference of network_selection.
830 // empty is auto mode, otherwise it is the operator alpha name
831 // in case there is no operator name, check the operator numeric
832 if (TextUtils.isEmpty(selectedNetworkOperatorName)) {
833 selectedNetworkOperatorName =
834 sp.getString(Phone.NETWORK_SELECTION_KEY + subId, "");
835 }
836 boolean isManualSelection;
fionaxud6aac662018-03-14 16:44:29 -0700837 // if restoring manual selection is controlled by framework, then get network
838 // selection from shared preference, otherwise get from real network indicators.
Daniel Brightebb4eb72020-02-18 15:16:33 -0800839 boolean restoreSelection = !mContext.getResources().getBoolean(
840 com.android.internal.R.bool.skip_restoring_network_selection);
fionaxud6aac662018-03-14 16:44:29 -0700841 if (restoreSelection) {
fionaxud6aac662018-03-14 16:44:29 -0700842 isManualSelection = !TextUtils.isEmpty(selectedNetworkOperatorName);
843 } else {
fionaxud6aac662018-03-14 16:44:29 -0700844 isManualSelection = phone.getServiceStateTracker().mSS.getIsManualSelection();
Amit Mahajana60be872015-01-15 16:05:08 -0800845 }
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700846
fionaxud6aac662018-03-14 16:44:29 -0700847 if (DBG) {
848 log("updateNetworkSelection()..." + "state = " + serviceState + " new network "
849 + (isManualSelection ? selectedNetworkOperatorName : ""));
850 }
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700851
irisykyang25202462018-11-13 14:49:54 +0800852 if (isManualSelection) {
853 mSelectedNetworkOperatorName.put(subId, selectedNetworkOperatorName);
854 shouldShowNotification(serviceState, subId);
Amit Mahajana60be872015-01-15 16:05:08 -0800855 } else {
irisykyang25202462018-11-13 14:49:54 +0800856 dismissNetworkSelectionNotification(subId);
857 clearUpNetworkSelectionNotificationParam(subId);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700858 }
859 } else {
Amit Mahajana60be872015-01-15 16:05:08 -0800860 if (DBG) log("updateNetworkSelection()..." + "state = " +
861 serviceState + " not updating network due to invalid subId " + subId);
irisykyang25202462018-11-13 14:49:54 +0800862 dismissNetworkSelectionNotificationForInactiveSubId();
863 }
864 }
865 }
866
867 private void dismissNetworkSelectionNotification(int subId) {
868 if (mSelectedUnavailableNotify.get(subId, false)) {
869 cancelNetworkSelection(subId);
870 mSelectedUnavailableNotify.remove(subId);
871 }
872 }
873
Sarah Chin010884c2023-06-14 15:55:08 -0700874 /**
875 * Dismiss the network selection "no service" notification for all inactive subscriptions.
876 */
877 public void dismissNetworkSelectionNotificationForInactiveSubId() {
irisykyang25202462018-11-13 14:49:54 +0800878 for (int i = 0; i < mSelectedUnavailableNotify.size(); i++) {
879 int subId = mSelectedUnavailableNotify.keyAt(i);
880 if (!mSubscriptionManager.isActiveSubId(subId)) {
881 dismissNetworkSelectionNotification(subId);
882 clearUpNetworkSelectionNotificationParam(subId);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700883 }
884 }
885 }
886
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700887 private void log(String msg) {
888 Log.d(LOG_TAG, msg);
889 }
Tyler Gunna584e2c2017-09-19 11:40:12 -0700890
891 private void logi(String msg) {
892 Log.i(LOG_TAG, msg);
893 }
irisykyang25202462018-11-13 14:49:54 +0800894
irisykyang25202462018-11-13 14:49:54 +0800895 private void shouldShowNotification(int serviceState, int subId) {
Rambo Wang5d5de762022-08-10 22:54:23 +0000896 // "Network selection unavailable" notification should only show when network selection is
897 // visible to the end user. Some CC items e.g. KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL
898 // can be overridden to hide the network selection to the end user. In this case, the
899 // notification is not shown to avoid confusion to the end user.
900 if (!shouldDisplayNetworkSelectOptions(subId)) {
901 logi("Skipping network selection unavailable notification due to carrier policy.");
902 return;
903 }
904
905 // In unstable network condition, the phone may go in and out of service. Add logic here to
906 // debounce the network selection notification. The notification only shows after phone is
907 // out of service, AND fulfills one of the two conditions below:
908 // - Out of service lasts {@link #NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIME_IN_MS}
909 // - Or has checked {@link #NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIMES} times
irisykyang25202462018-11-13 14:49:54 +0800910 if (serviceState == ServiceState.STATE_OUT_OF_SERVICE) {
911 if (mPreviousServiceState.get(subId, STATE_UNKNOWN_SERVICE)
912 != ServiceState.STATE_OUT_OF_SERVICE) {
913 mOOSTimestamp.put(subId, getTimeStamp());
914 }
915 if ((getTimeStamp() - mOOSTimestamp.get(subId, 0L)
916 >= NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIME_IN_MS)
917 || mPendingEventCounter.get(subId, 0)
918 > NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIMES) {
919 showNetworkSelection(mSelectedNetworkOperatorName.get(subId), subId);
920 clearUpNetworkSelectionNotificationParam(subId);
921 } else {
922 startPendingNetworkSelectionNotification(subId);
923 }
924 } else {
925 dismissNetworkSelectionNotification(subId);
926 }
927 mPreviousServiceState.put(subId, serviceState);
928 if (DBG) {
929 log("shouldShowNotification()..." + " subId = " + subId
930 + " serviceState = " + serviceState
931 + " mOOSTimestamp = " + mOOSTimestamp
932 + " mPendingEventCounter = " + mPendingEventCounter);
933 }
934 }
935
936 private void startPendingNetworkSelectionNotification(int subId) {
937 if (!mHandler.hasMessages(EVENT_PENDING_NETWORK_SELECTION_NOTIFICATION, subId)) {
938 if (DBG) {
939 log("startPendingNetworkSelectionNotification: subId = " + subId);
940 }
941 mHandler.sendMessageDelayed(
942 mHandler.obtainMessage(EVENT_PENDING_NETWORK_SELECTION_NOTIFICATION, subId),
943 NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIME_IN_MS);
944 mPendingEventCounter.put(subId, mPendingEventCounter.get(subId, 0) + 1);
945 }
946 }
947
948 private void clearUpNetworkSelectionNotificationParam(int subId) {
949 if (mHandler.hasMessages(EVENT_PENDING_NETWORK_SELECTION_NOTIFICATION, subId)) {
950 mHandler.removeMessages(EVENT_PENDING_NETWORK_SELECTION_NOTIFICATION, subId);
951 }
952 mPreviousServiceState.remove(subId);
953 mOOSTimestamp.remove(subId);
954 mPendingEventCounter.remove(subId);
955 mSelectedNetworkOperatorName.remove(subId);
956 }
957
958 private static long getTimeStamp() {
959 return SystemClock.elapsedRealtime();
960 }
Rambo Wang5d5de762022-08-10 22:54:23 +0000961
962 // TODO(b/243010310): merge methods below with Settings#MobileNetworkUtils and optimize them.
963 // The methods below are copied from com.android.settings.network.telephony.MobileNetworkUtils
964 // to make sure the network selection unavailable notification should not show when network
965 // selection menu is not visible to the end user in Settings app.
966 private boolean shouldDisplayNetworkSelectOptions(int subId) {
967 final TelephonyManager telephonyManager = mTelephonyManager.createForSubscriptionId(subId);
968 final CarrierConfigManager carrierConfigManager = mContext.getSystemService(
969 CarrierConfigManager.class);
970 final PersistableBundle carrierConfig = carrierConfigManager.getConfigForSubId(subId);
971
972 if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID
973 || carrierConfig == null
974 || !carrierConfig.getBoolean(
975 CarrierConfigManager.KEY_OPERATOR_SELECTION_EXPAND_BOOL)
976 || carrierConfig.getBoolean(
977 CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL)
978 || (carrierConfig.getBoolean(CarrierConfigManager.KEY_CSP_ENABLED_BOOL)
979 && !telephonyManager.isManualNetworkSelectionAllowed())) {
980 return false;
981 }
982
983 if (isWorldMode(carrierConfig)) {
984 final int networkMode = RadioAccessFamily.getNetworkTypeFromRaf(
985 (int) telephonyManager.getAllowedNetworkTypesForReason(
986 TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER));
987 if (networkMode == RILConstants.NETWORK_MODE_LTE_CDMA_EVDO) {
988 return false;
989 }
990 if (shouldSpeciallyUpdateGsmCdma(telephonyManager, carrierConfig)) {
991 return false;
992 }
993 if (networkMode == RILConstants.NETWORK_MODE_LTE_GSM_WCDMA) {
994 return true;
995 }
996 }
997
998 return isGsmBasicOptions(telephonyManager, carrierConfig);
999 }
1000
1001 private static boolean isWorldMode(@NonNull PersistableBundle carrierConfig) {
1002 return carrierConfig.getBoolean(CarrierConfigManager.KEY_WORLD_MODE_ENABLED_BOOL);
1003 }
1004
1005 private static boolean shouldSpeciallyUpdateGsmCdma(@NonNull TelephonyManager telephonyManager,
1006 @NonNull PersistableBundle carrierConfig) {
1007 if (!isWorldMode(carrierConfig)) {
1008 return false;
1009 }
1010
1011 final int networkMode = RadioAccessFamily.getNetworkTypeFromRaf(
1012 (int) telephonyManager.getAllowedNetworkTypesForReason(
1013 TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER));
1014 if (networkMode == RILConstants.NETWORK_MODE_LTE_TDSCDMA_GSM
1015 || networkMode == RILConstants.NETWORK_MODE_LTE_TDSCDMA_GSM_WCDMA
1016 || networkMode == RILConstants.NETWORK_MODE_LTE_TDSCDMA
1017 || networkMode == RILConstants.NETWORK_MODE_LTE_TDSCDMA_WCDMA
1018 || networkMode
1019 == RILConstants.NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA
1020 || networkMode == RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA) {
1021 if (!isTdscdmaSupported(telephonyManager, carrierConfig)) {
1022 return true;
1023 }
1024 }
1025
1026 return false;
1027 }
1028
1029 private static boolean isTdscdmaSupported(@NonNull TelephonyManager telephonyManager,
1030 @NonNull PersistableBundle carrierConfig) {
1031 if (carrierConfig.getBoolean(CarrierConfigManager.KEY_SUPPORT_TDSCDMA_BOOL)) {
1032 return true;
1033 }
1034 final String[] numericArray = carrierConfig.getStringArray(
1035 CarrierConfigManager.KEY_SUPPORT_TDSCDMA_ROAMING_NETWORKS_STRING_ARRAY);
1036 if (numericArray == null) {
1037 return false;
1038 }
1039 final ServiceState serviceState = telephonyManager.getServiceState();
1040 final String operatorNumeric =
1041 (serviceState != null) ? serviceState.getOperatorNumeric() : null;
1042 if (operatorNumeric == null) {
1043 return false;
1044 }
1045 for (String numeric : numericArray) {
1046 if (operatorNumeric.equals(numeric)) {
1047 return true;
1048 }
1049 }
1050 return false;
1051 }
1052
1053 private static boolean isGsmBasicOptions(@NonNull TelephonyManager telephonyManager,
1054 @NonNull PersistableBundle carrierConfig) {
1055 if (!carrierConfig.getBoolean(
1056 CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL)
1057 && carrierConfig.getBoolean(CarrierConfigManager.KEY_WORLD_PHONE_BOOL)) {
1058 return true;
1059 }
1060
1061 if (telephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
1062 return true;
1063 }
1064
1065 return false;
1066 }
1067 // END of TODO:(b/243010310): merge methods above with Settings#MobileNetworkUtils and optimize.
Santos Cordon7d4ddf62013-07-10 11:58:08 -07001068}