Refactored no data due to roaming notification

Instead of listening to ACTION_ANY_DATA_CONNECTION_STATE_CHANGED,
we now listent to mobile data settings changed, data roaming
settings changed, carrier config changed, roaming status changed,
and default data subscription changed event. This will make the
notification show/hide become more reliable.

Test: manual
bug: 63027846
Change-Id: I413711c4af6374609f87f5aad63eb1a09fd22655
diff --git a/src/com/android/phone/DumpsysHandler.java b/src/com/android/phone/DumpsysHandler.java
index a0277fc..47f6105 100644
--- a/src/com/android/phone/DumpsysHandler.java
+++ b/src/com/android/phone/DumpsysHandler.java
@@ -15,6 +15,7 @@
 
     public static void dump(Context context, FileDescriptor fd, PrintWriter writer,
             String[] args) {
+        PhoneGlobals.getInstance().dump(fd, writer, args);
         // Dump OMTP visual voicemail log.
         VvmDumpHandler.dump(context, fd, writer, args);
     }
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index 1d61c8e..3b9ea03 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -43,6 +43,8 @@
 import android.telephony.CarrierConfigManager;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.LocalLog;
 import android.util.Log;
 import android.widget.Toast;
 
@@ -53,19 +55,26 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.SettingsObserver;
 import com.android.internal.telephony.TelephonyCapabilities;
 import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.dataconnection.DataConnectionReasons;
+import com.android.internal.telephony.dataconnection.DataConnectionReasons.DataDisallowedReasonType;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.phone.common.CallLogAsync;
 import com.android.phone.settings.SettingsConstants;
 import com.android.phone.vvm.CarrierVvmPackageInstalledReceiver;
 import com.android.services.telephony.sip.SipUtil;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
 /**
  * Global state for the telephony subsystem when running in the primary
  * phone process.
  */
 public class PhoneGlobals extends ContextWrapper {
-    public static final String LOG_TAG = "PhoneApp";
+    public static final String LOG_TAG = "PhoneGlobals";
 
     /**
      * Phone app-wide debug level:
@@ -96,6 +105,8 @@
     private static final int EVENT_DATA_ROAMING_OK = 11;
     private static final int EVENT_UNSOL_CDMA_INFO_RECORD = 12;
     private static final int EVENT_RESTART_SIP = 13;
+    private static final int EVENT_DATA_ROAMING_SETTINGS_CHANGED = 14;
+    private static final int EVENT_MOBILE_DATA_SETTINGS_CHANGED = 15;
 
     // The MMI codes are also used by the InCallScreen.
     public static final int MMI_INITIATE = 51;
@@ -146,7 +157,7 @@
     private Activity mPUKEntryActivity;
     private ProgressDialog mPUKEntryProgressDialog;
 
-    private boolean mDataDisconnectedDueToRoaming = false;
+    private boolean mNoDataDueToRoaming = false;
 
     private WakeState mWakeState = WakeState.SLEEP;
 
@@ -157,16 +168,22 @@
 
     private UpdateLock mUpdateLock;
 
+    private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    private final LocalLog mDataRoamingNotifLog = new LocalLog(50);
+
     // Broadcast receiver for various intent broadcasts (see onCreate())
     private final BroadcastReceiver mReceiver = new PhoneAppBroadcastReceiver();
 
     private final CarrierVvmPackageInstalledReceiver mCarrierVvmPackageInstalledReceiver =
             new CarrierVvmPackageInstalledReceiver();
 
+    private final SettingsObserver mSettingsObserver;
+
     Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
             PhoneConstants.State phoneState;
+            if (VDBG) Log.v(LOG_TAG, "event=" + msg.what);
             switch (msg.what) {
                 // TODO: This event should be handled by the lock screen, just
                 // like the "SIM missing" and "Sim locked" cases (bug 1804111).
@@ -233,6 +250,10 @@
                         SipUtil.startSipService();
                     }
                     break;
+                case EVENT_DATA_ROAMING_SETTINGS_CHANGED:
+                case EVENT_MOBILE_DATA_SETTINGS_CHANGED:
+                    updateDataRoamingStatus();
+                    break;
             }
         }
     };
@@ -240,6 +261,7 @@
     public PhoneGlobals(Context context) {
         super(context);
         sMe = this;
+        mSettingsObserver = new SettingsObserver(context, mHandler);
     }
 
     public void onCreate() {
@@ -315,7 +337,7 @@
 
             configLoader = CarrierConfigLoader.init(this);
 
-            // Create the CallNotifer singleton, which handles
+            // Create the CallNotifier singleton, which handles
             // asynchronous events from the telephony layer (like
             // launching the incoming-call UI when an incoming call comes
             // in.)
@@ -332,11 +354,12 @@
             // Register for misc other intent broadcasts.
             IntentFilter intentFilter =
                     new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
-            intentFilter.addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
             intentFilter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
             intentFilter.addAction(TelephonyIntents.ACTION_RADIO_TECHNOLOGY_CHANGED);
             intentFilter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
             intentFilter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
+            intentFilter.addAction(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
+            intentFilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
             registerReceiver(mReceiver, intentFilter);
 
             mCarrierVvmPackageInstalledReceiver.register(this);
@@ -414,6 +437,27 @@
         return configLoader.getConfigForSubId(subId);
     }
 
+    private void registerSettingsObserver() {
+        mSettingsObserver.unobserve();
+        String dataRoamingSetting = Settings.Global.DATA_ROAMING;
+        String mobileDataSetting = Settings.Global.MOBILE_DATA;
+        if (TelephonyManager.getDefault().getSimCount() > 1) {
+            int subId = mDefaultDataSubId;
+            if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                dataRoamingSetting += subId;
+                mobileDataSetting += subId;
+            }
+        }
+
+        // Listen for user data roaming setting changed event
+        mSettingsObserver.observe(Settings.Global.getUriFor(dataRoamingSetting),
+                EVENT_DATA_ROAMING_SETTINGS_CHANGED);
+
+        // Listen for mobile data setting changed event
+        mSettingsObserver.observe(Settings.Global.getUriFor(mobileDataSetting),
+                EVENT_MOBILE_DATA_SETTINGS_CHANGED);
+    }
+
     /**
      * Sets the activity responsible for un-PUK-blocking the device
      * so that we may close it when we receive a positive result.
@@ -653,50 +697,6 @@
                     airplaneMode = AIRPLANE_ON;
                 }
                 handleAirplaneModeChange(context, airplaneMode);
-            } else if (action.equals(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) {
-                int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
-                        SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-                int phoneId = SubscriptionManager.getPhoneId(subId);
-                final String apnType = intent.getStringExtra(PhoneConstants.DATA_APN_TYPE_KEY);
-                final String state = intent.getStringExtra(PhoneConstants.STATE_KEY);
-                final String reason = intent.getStringExtra(PhoneConstants.STATE_CHANGE_REASON_KEY);
-                if (VDBG) {
-                    Log.d(LOG_TAG, "mReceiver: ACTION_ANY_DATA_CONNECTION_STATE_CHANGED");
-                    Log.d(LOG_TAG, "- state: " + state);
-                    Log.d(LOG_TAG, "- reason: " + reason);
-                    Log.d(LOG_TAG, "- subId: " + subId);
-                }
-                Phone phone = SubscriptionManager.isValidPhoneId(phoneId) ?
-                        PhoneFactory.getPhone(phoneId) : PhoneFactory.getDefaultPhone();
-
-                // If the apn type of data connection state changed event is NOT default,
-                // ignore the broadcast intent and avoid action.
-                if (!PhoneConstants.APN_TYPE_DEFAULT.equals(apnType)) {
-                    if (VDBG) Log.d(LOG_TAG, "Ignore broadcast intent as not default apn type");
-                    return;
-                }
-
-                // The "data disconnected due to roaming" notification is shown
-                // if (a) you have the "data roaming" feature turned off, and
-                // (b) you just lost data connectivity because you're roaming.
-                // (c) if we haven't shown the notification for this disconnection earlier.
-                // (d) if data was enabled for the sim
-                if (!mDataDisconnectedDueToRoaming
-                        && PhoneConstants.DataState.DISCONNECTED.name().equals(state)
-                        && Phone.REASON_ROAMING_ON.equals(reason)
-                        && !phone.getDataRoamingEnabled()
-                        && phone.getDataEnabled()) {
-                    // Notify the user that data call is disconnected due to roaming. Note that
-                    // calling this multiple times will not cause multiple notifications.
-                    mHandler.sendEmptyMessage(EVENT_DATA_ROAMING_DISCONNECTED);
-                    mDataDisconnectedDueToRoaming = true;
-                } else if (mDataDisconnectedDueToRoaming
-                        && PhoneConstants.DataState.CONNECTED.name().equals(state)) {
-                    // Cancel the notification when data is available. Note it is okay to call this
-                    // even if the notification doesn't exist.
-                    mHandler.sendEmptyMessage(EVENT_DATA_ROAMING_OK);
-                    mDataDisconnectedDueToRoaming = false;
-                }
             } else if ((action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) &&
                     (mPUKEntryActivity != null)) {
                 // if an attempt to un-PUK-lock the device was made, while we're
@@ -735,6 +735,19 @@
                 } else {
                     Log.w(LOG_TAG, "phoneInEcm is null.");
                 }
+            } else if (action.equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
+                // Roaming status could be overridden by carrier config, so we need to update it.
+                if (VDBG) Log.v(LOG_TAG, "carrier config changed.");
+                updateDataRoamingStatus();
+            } else if (action.equals(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) {
+                // We also need to pay attention when default data subscription changes.
+                if (VDBG) Log.v(LOG_TAG, "default data sub changed.");
+                mDefaultDataSubId = SubscriptionManager.getDefaultDataSubscriptionId();
+                registerSettingsObserver();
+                Phone phone = getPhone(mDefaultDataSubId);
+                if (phone != null) {
+                    updateDataRoamingStatus();
+                }
             }
         }
     }
@@ -747,6 +760,7 @@
          * future.
          */
 
+        if (VDBG) Log.v(LOG_TAG, "handleServiceStateChanged");
         // If service just returned, start sending out the queued messages
         Bundle extras = intent.getExtras();
         if (extras != null) {
@@ -756,10 +770,54 @@
                 int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
                         SubscriptionManager.INVALID_SUBSCRIPTION_ID);
                 notificationMgr.updateNetworkSelection(state, subId);
+
+                if (VDBG) {
+                    Log.v(LOG_TAG, "subId=" + subId + ",mDefaultDataSubId="
+                            + mDefaultDataSubId + ",ss roaming=" + ss.getDataRoaming());
+                }
+                if (subId == mDefaultDataSubId) {
+                    updateDataRoamingStatus();
+                }
             }
         }
     }
 
+    /**
+     * When roaming, if mobile data cannot be established due to data roaming not enabled, we need
+     * to notify the user so they can enable it through settings. Vise versa if the condition
+     * changes, we need to dismiss the notification.
+     */
+    private void updateDataRoamingStatus() {
+        if (VDBG) Log.v(LOG_TAG, "updateDataRoamingStatus");
+        Phone phone = getPhone(mDefaultDataSubId);
+        if (phone == null) {
+            Log.w(LOG_TAG, "Can't get phone with sub id = " + mDefaultDataSubId);
+            return;
+        }
+
+        DataConnectionReasons reasons = new DataConnectionReasons();
+        boolean dataAllowed = phone.isDataAllowed(reasons);
+        mDataRoamingNotifLog.log("dataAllowed=" + dataAllowed + ", reasons=" + reasons);
+        if (VDBG) Log.v(LOG_TAG, "dataAllowed=" + dataAllowed + ", reasons=" + reasons);
+        if (!mNoDataDueToRoaming
+                && !dataAllowed
+                && reasons.containsOnly(DataDisallowedReasonType.ROAMING_DISABLED)) {
+            // If the only reason of no data is data roaming disabled, then we notify the user
+            // so the user can turn on data roaming.
+            mNoDataDueToRoaming = true;
+            Log.d(LOG_TAG, "Show roaming disconnected notification");
+            mDataRoamingNotifLog.log("Show");
+            mHandler.sendEmptyMessage(EVENT_DATA_ROAMING_DISCONNECTED);
+        } else if (mNoDataDueToRoaming && (dataAllowed
+                || !reasons.containsOnly(DataDisallowedReasonType.ROAMING_DISABLED))) {
+            // Otherwise dismiss the notification we showed earlier.
+            mNoDataDueToRoaming = false;
+            Log.d(LOG_TAG, "Dismiss roaming disconnected notification");
+            mDataRoamingNotifLog.log("Hide. data allowed=" + dataAllowed + ", reasons=" + reasons);
+            mHandler.sendEmptyMessage(EVENT_DATA_ROAMING_OK);
+        }
+    }
+
     public Phone getPhoneInEcm() {
         return phoneInEcm;
     }
@@ -807,4 +865,25 @@
     public void setShouldCheckVisualVoicemailConfigurationForMwi(int subId, boolean enabled) {
         notificationMgr.setShouldCheckVisualVoicemailConfigurationForMwi(subId, enabled);
     }
+
+    /**
+     * Dump the state of the object, add calls to other objects as desired.
+     *
+     * @param fd File descriptor
+     * @param printWriter Print writer
+     * @param args Arguments
+     */
+    public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
+        IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
+        pw.println("------- PhoneGlobals -------");
+        pw.increaseIndent();
+        pw.println("mNoDataDueToRoaming=" + mNoDataDueToRoaming);
+        pw.println("mDefaultDataSubId=" + mDefaultDataSubId);
+        pw.println("mDataRoamingNotifLog:");
+        pw.increaseIndent();
+        mDataRoamingNotifLog.dump(fd, pw, args);
+        pw.decreaseIndent();
+        pw.decreaseIndent();
+        pw.println("------- End PhoneGlobals -------");
+    }
 }
diff --git a/tests/src/com/android/phone/PhoneGlobalsTest.java b/tests/src/com/android/phone/PhoneGlobalsTest.java
deleted file mode 100644
index b862ee3..0000000
--- a/tests/src/com/android/phone/PhoneGlobalsTest.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.phone;
-
-import android.content.Intent;
-import android.os.Handler;
-import android.os.Message;
-import android.support.test.runner.AndroidJUnit4;
-
-import com.android.TelephonyTestBase;
-import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.PhoneFactory;
-import com.android.internal.telephony.TelephonyIntents;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-
-import java.lang.reflect.Field;
-
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-@RunWith(AndroidJUnit4.class)
-public class PhoneGlobalsTest extends TelephonyTestBase {
-
-    private Phone mPhone = PhoneFactory.getDefaultPhone();
-
-    private PhoneGlobals mPhoneGlobals = PhoneGlobals.getInstance();
-
-    private Handler mHandler = mock(Handler.class);
-
-    private static final int EVENT_DATA_ROAMING_DISCONNECTED = 10;
-
-    @Before
-    public void setUp() throws Exception {
-        super.setUp();
-        Field handler = PhoneGlobals.class.getDeclaredField("mHandler");
-        handler.setAccessible(true);
-        handler.set(mPhoneGlobals, mHandler);
-    }
-
-    @Test
-    public void testDataDisconnectedNotification() {
-        mPhone.setDataRoamingEnabled(false);
-        // Test no notification sent out when data is disabled, data raoming enabled and data
-        // disconnected because of roaming.
-        mPhone.setDataEnabled(false);
-        Intent intent = new Intent(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
-        intent.putExtra(PhoneConstants.DATA_APN_TYPE_KEY,
-                PhoneConstants.APN_TYPE_DEFAULT);
-        intent.putExtra(PhoneConstants.STATE_KEY, PhoneConstants.DataState.DISCONNECTED.name());
-        intent.putExtra(PhoneConstants.STATE_CHANGE_REASON_KEY, Phone.REASON_ROAMING_ON);
-        mContext.sendBroadcast(intent);
-
-        waitForMs(300);
-
-        ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
-        verify(mHandler, atLeast(0)).sendMessageAtTime(any(Message.class), anyLong());
-        boolean flag = true;
-        for (Message msg : messageArgumentCaptor.getAllValues()) {
-            if (msg.what == EVENT_DATA_ROAMING_DISCONNECTED) {
-                flag = false;
-            }
-        }
-        assertTrue(flag);
-
-
-        // Test notification sent out when data is enabled, data raoming enabled and data
-        // disconnected because of roaming.
-        mPhone.setDataEnabled(true);
-        mContext.sendBroadcast(intent);
-
-        waitForMs(300);
-
-
-        verify(mHandler, atLeast(1)).sendMessageAtTime(messageArgumentCaptor.capture(), anyLong());
-        flag = false;
-        for (Message msg : messageArgumentCaptor.getAllValues()) {
-            if (msg.what == EVENT_DATA_ROAMING_DISCONNECTED) {
-                flag = true;
-            }
-        }
-        assertTrue(flag);
-    }
-}