Merge "protected android.telephony.action.CARRIER_SIGNAL_PCO_VALUE broadcast"
diff --git a/res/values/config.xml b/res/values/config.xml
index 08a84f8..9f8cc81 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -313,4 +313,8 @@
 
     <!-- Whether or not to show notifications for when bluetooth connection is bad during a call -->
     <bool name="enable_bluetooth_call_quality_notification">false</bool>
+
+    <!-- The package names which can request thermal mitigation. -->
+    <string-array name="thermal_mitigation_allowlisted_packages" translatable="false">
+    </string-array>
 </resources>
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 69c2b8a..ebe6c4d 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -333,6 +333,7 @@
 
     /** The singleton instance. */
     private static PhoneInterfaceManager sInstance;
+    private static List<String> sThermalMitigationAllowlistedPackages = new ArrayList<>();
 
     private PhoneGlobals mApp;
     private CallManager mCM;
@@ -9404,11 +9405,43 @@
         return TelephonyManager.THERMAL_MITIGATION_RESULT_SUCCESS;
     }
 
+    private static List<String> getThermalMitigationAllowlist(Context context) {
+        if (sThermalMitigationAllowlistedPackages.isEmpty()) {
+            for (String pckg : context.getResources()
+                    .getStringArray(R.array.thermal_mitigation_allowlisted_packages)) {
+                sThermalMitigationAllowlistedPackages.add(pckg);
+            }
+        }
+
+        return sThermalMitigationAllowlistedPackages;
+    }
+
+    /**
+     * Used by shell commands to add an authorized package name for thermal mitigation.
+     * @param packageName name of package to be allowlisted
+     * @param context
+     */
+    static void addPackageToThermalMitigationAllowlist(String packageName, Context context) {
+        sThermalMitigationAllowlistedPackages = getThermalMitigationAllowlist(context);
+        sThermalMitigationAllowlistedPackages.add(packageName);
+    }
+
+    /**
+     * Used by shell commands to remove an authorized package name for thermal mitigation.
+     * @param packageName name of package to remove from allowlist
+     * @param context
+     */
+    static void removePackageFromThermalMitigationAllowlist(String packageName, Context context) {
+        sThermalMitigationAllowlistedPackages = getThermalMitigationAllowlist(context);
+        sThermalMitigationAllowlistedPackages.remove(packageName);
+    }
+
     /**
      * Thermal mitigation request to control functionalities at modem.
      *
      * @param subId the id of the subscription.
      * @param thermalMitigationRequest holds all necessary information to be passed down to modem.
+     * @param callingPackage the package name of the calling package.
      *
      * @return thermalMitigationResult enum as defined in android.telephony.Annotation.
      */
@@ -9416,9 +9449,17 @@
     @ThermalMitigationResult
     public int sendThermalMitigationRequest(
             int subId,
-            ThermalMitigationRequest thermalMitigationRequest) throws IllegalArgumentException {
+            ThermalMitigationRequest thermalMitigationRequest,
+            String callingPackage) throws IllegalArgumentException {
         enforceModifyPermission();
 
+        mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
+        if (!getThermalMitigationAllowlist(getDefaultPhone().getContext())
+                .contains(callingPackage)) {
+            throw new SecurityException("Calling package must be configured in the device config. "
+                    + "calling package: " + callingPackage);
+        }
+
         WorkSource workSource = getWorkSource(Binder.getCallingUid());
         final long identity = Binder.clearCallingIdentity();
 
diff --git a/src/com/android/phone/ServiceStateProvider.java b/src/com/android/phone/ServiceStateProvider.java
index a7d27d5..63bc600 100644
--- a/src/com/android/phone/ServiceStateProvider.java
+++ b/src/com/android/phone/ServiceStateProvider.java
@@ -18,6 +18,7 @@
 
 import static android.provider.Telephony.ServiceStateTable;
 import static android.provider.Telephony.ServiceStateTable.CONTENT_URI;
+import static android.provider.Telephony.ServiceStateTable.DATA_NETWORK_TYPE;
 import static android.provider.Telephony.ServiceStateTable.IS_MANUAL_NETWORK_SELECTION;
 import static android.provider.Telephony.ServiceStateTable.VOICE_REG_STATE;
 import static android.provider.Telephony.ServiceStateTable.getUriForSubscriptionId;
@@ -257,6 +258,7 @@
         IS_USING_CARRIER_AGGREGATION,
         OPERATOR_ALPHA_LONG_RAW,
         OPERATOR_ALPHA_SHORT_RAW,
+        DATA_NETWORK_TYPE,
     };
 
     @Override
@@ -392,6 +394,7 @@
             final int is_using_carrier_aggregation = (ss.isUsingCarrierAggregation()) ? 1 : 0;
             final String operator_alpha_long_raw = ss.getOperatorAlphaLongRaw();
             final String operator_alpha_short_raw = ss.getOperatorAlphaShortRaw();
+            final int data_network_type = ss.getDataNetworkType();
 
             return buildSingleRowResult(projection, sColumns, new Object[] {
                     voice_reg_state,
@@ -418,6 +421,7 @@
                     is_using_carrier_aggregation,
                     operator_alpha_long_raw,
                     operator_alpha_short_raw,
+                    data_network_type,
             });
         }
     }
@@ -480,6 +484,10 @@
             context.getContentResolver().notifyChange(
                     getUriForSubscriptionIdAndField(subId, DATA_ROAMING_TYPE), null, false);
         }
+        if (firstUpdate || dataNetworkTypeChanged(oldSS, newSS)) {
+            context.getContentResolver().notifyChange(
+                    getUriForSubscriptionIdAndField(subId, DATA_NETWORK_TYPE), null, false);
+        }
     }
 
     private static boolean voiceRegStateChanged(ServiceState oldSS, ServiceState newSS) {
@@ -498,6 +506,10 @@
         return oldSS.getDataRoamingType() != newSS.getDataRoamingType();
     }
 
+    private static boolean dataNetworkTypeChanged(ServiceState oldSS, ServiceState newSS) {
+        return oldSS.getDataNetworkType() != newSS.getDataNetworkType();
+    }
+
     /**
      * Notify interested apps that the ServiceState has changed.
      *
@@ -517,7 +529,8 @@
         // If oldSS is null and newSS is not (e.g. first update of service state) this will also
         // notify
         if (oldSS == null || voiceRegStateChanged(oldSS, newSS) || dataRegStateChanged(oldSS, newSS)
-                || voiceRoamingTypeChanged(oldSS, newSS) || dataRoamingTypeChanged(oldSS, newSS)) {
+                || voiceRoamingTypeChanged(oldSS, newSS) || dataRoamingTypeChanged(oldSS, newSS)
+                || dataNetworkTypeChanged(oldSS, newSS)) {
             context.getContentResolver().notifyChange(getUriForSubscriptionId(subId), null, false);
         }
     }
diff --git a/src/com/android/phone/TelephonyShellCommand.java b/src/com/android/phone/TelephonyShellCommand.java
index 87dc868..b2e1a99 100644
--- a/src/com/android/phone/TelephonyShellCommand.java
+++ b/src/com/android/phone/TelephonyShellCommand.java
@@ -128,6 +128,10 @@
     // Check if a package has carrier privileges on any SIM, regardless of subId/phoneId.
     private static final String HAS_CARRIER_PRIVILEGES_COMMAND = "has-carrier-privileges";
 
+    private static final String THERMAL_MITIGATION_COMMAND = "thermal-mitigation";
+    private static final String ALLOW_THERMAL_MITIGATION_PACKAGE_SUBCOMMAND = "allow-package";
+    private static final String DISALLOW_THERMAL_MITIGATION_PACKAGE_SUBCOMMAND = "disallow-package";
+
     // Take advantage of existing methods that already contain permissions checks when possible.
     private final ITelephony mInterface;
 
@@ -264,6 +268,8 @@
                 return handleUnattendedReboot();
             case HAS_CARRIER_PRIVILEGES_COMMAND:
                 return handleHasCarrierPrivilegesCommand();
+            case THERMAL_MITIGATION_COMMAND:
+                return handleThermalMitigationCommand();
             default: {
                 return handleDefaultCommands(cmd);
             }
@@ -410,6 +416,16 @@
         pw.println("    1 if the call would have been intercepted, 0 otherwise.");
     }
 
+    private void onHelpThermalMitigation() {
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("Thermal mitigation commands");
+        pw.println("  thermal-mitigation allow-package PACKAGE_NAME");
+        pw.println("    Set the package as one of authorized packages for thermal mitigation.");
+        pw.println("  thermal-mitigation disallow-package PACKAGE_NAME");
+        pw.println("    Remove the package from one of the authorized packages for thermal "
+                + "mitigation.");
+    }
+
     private void onHelpDataTestMode() {
         PrintWriter pw = getOutPrintWriter();
         pw.println("Mobile Data Test Mode Commands:");
@@ -696,6 +712,36 @@
         return -1;
     }
 
+    private int handleThermalMitigationCommand() {
+        String arg = getNextArg();
+        String packageName = getNextArg();
+        if (arg == null || packageName == null) {
+            onHelpThermalMitigation();
+            return 0;
+        }
+
+        if (!checkShellUid()) {
+            return -1;
+        }
+
+        switch (arg) {
+            case ALLOW_THERMAL_MITIGATION_PACKAGE_SUBCOMMAND: {
+                PhoneInterfaceManager.addPackageToThermalMitigationAllowlist(packageName, mContext);
+                return 0;
+            }
+            case DISALLOW_THERMAL_MITIGATION_PACKAGE_SUBCOMMAND: {
+                PhoneInterfaceManager.removePackageFromThermalMitigationAllowlist(packageName,
+                        mContext);
+                return 0;
+            }
+            default:
+                onHelpThermalMitigation();
+        }
+
+        return -1;
+
+    }
+
     private int handleD2dCommand() {
         String arg = getNextArg();
         if (arg == null) {
diff --git a/tests/src/com/android/phone/ServiceStateProviderTest.java b/tests/src/com/android/phone/ServiceStateProviderTest.java
index 32e5f26..55fdada 100644
--- a/tests/src/com/android/phone/ServiceStateProviderTest.java
+++ b/tests/src/com/android/phone/ServiceStateProviderTest.java
@@ -18,6 +18,7 @@
 
 import static android.provider.Telephony.ServiceStateTable;
 import static android.provider.Telephony.ServiceStateTable.getUriForSubscriptionId;
+import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_HOME;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -31,8 +32,11 @@
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.net.Uri;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.test.mock.MockContentResolver;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -81,15 +85,23 @@
         ServiceStateProvider.IS_USING_CARRIER_AGGREGATION,
         ServiceStateProvider.OPERATOR_ALPHA_LONG_RAW,
         ServiceStateProvider.OPERATOR_ALPHA_SHORT_RAW,
+        ServiceStateTable.DATA_NETWORK_TYPE,
     };
 
+    // Exception used internally to verify if the Resolver#notifyChange has been called.
+    private class TestNotifierException extends RuntimeException {
+        TestNotifierException() {
+            super();
+        }
+    }
+
     @Before
     public void setUp() throws Exception {
         mContext = mock(Context.class);
         mContentResolver = new MockContentResolver() {
             @Override
             public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
-                throw new RuntimeException("notifyChange!");
+                throw new TestNotifierException();
             }
         };
         doReturn(mContentResolver).when(mContext).getContentResolver();
@@ -183,6 +195,7 @@
         final int isUsingCarrierAggregation = (ss.isUsingCarrierAggregation()) ? 1 : 0;
         final String operatorAlphaLongRaw = ss.getOperatorAlphaLongRaw();
         final String operatorAlphaShortRaw = ss.getOperatorAlphaShortRaw();
+        final int dataNetworkType = ss.getDataNetworkType();
 
         assertEquals(voiceRegState, cursor.getInt(0));
         assertEquals(dataRegState, cursor.getInt(1));
@@ -206,6 +219,7 @@
         assertEquals(isUsingCarrierAggregation, cursor.getInt(19));
         assertEquals(operatorAlphaLongRaw, cursor.getString(20));
         assertEquals(operatorAlphaShortRaw, cursor.getString(21));
+        assertEquals(dataNetworkType, cursor.getInt(22));
     }
 
     /**
@@ -226,21 +240,12 @@
         newSS.setCdmaSystemAndNetworkId(0, 0);
 
         // Test that notifyChange is not called for these fields
-        boolean notifyChangeWasCalled = false;
-        try {
-            ServiceStateProvider.notifyChangeForSubIdAndField(mContext, oldSS, newSS, subId);
-        } catch (RuntimeException e) {
-            final String message = e.getMessage();
-            if (message != null &&  message.equals("notifyChange!")) {
-                notifyChangeWasCalled = true;
-            }
-        }
-        assertFalse(notifyChangeWasCalled);
+        assertFalse(notifyChangeCalledForSubIdAndField(oldSS, newSS, subId));
     }
 
     @Test
     @SmallTest
-    public void testNotifyChanged() {
+    public void testNotifyChanged_noStateUpdated() {
         int subId = 0;
 
         ServiceState oldSS = new ServiceState();
@@ -251,57 +256,84 @@
         copyOfOldSS.setStateOutOfService();
         copyOfOldSS.setVoiceRegState(ServiceState.STATE_OUT_OF_SERVICE);
 
+        // Test that notifyChange is not called with no change in notifyChangeForSubIdAndField
+        assertFalse(notifyChangeCalledForSubId(oldSS, copyOfOldSS, subId));
+
+        // Test that notifyChange is not called with no change in notifyChangeForSubId
+        assertFalse(notifyChangeCalledForSubIdAndField(oldSS, copyOfOldSS, subId));
+    }
+
+    @Test
+    @SmallTest
+    public void testNotifyChanged_voiceRegStateUpdated() {
+        int subId = 0;
+
+        ServiceState oldSS = new ServiceState();
+        oldSS.setStateOutOfService();
+        oldSS.setVoiceRegState(ServiceState.STATE_OUT_OF_SERVICE);
+
         ServiceState newSS = new ServiceState();
         newSS.setStateOutOfService();
         newSS.setVoiceRegState(ServiceState.STATE_POWER_OFF);
 
-        // Test that notifyChange is not called with no change in notifyChangeForSubIdAndField
-        boolean notifyChangeWasCalled = false;
-        try {
-            ServiceStateProvider.notifyChangeForSubIdAndField(mContext, oldSS, copyOfOldSS, subId);
-        } catch (RuntimeException e) {
-            final String message = e.getMessage();
-            if (message != null &&  message.equals("notifyChange!")) {
-                notifyChangeWasCalled = true;
-            }
-        }
-        assertFalse(notifyChangeWasCalled);
-
-        // Test that notifyChange is not called with no change in notifyChangeForSubId
-        notifyChangeWasCalled = false;
-        try {
-            ServiceStateProvider.notifyChangeForSubId(mContext, oldSS, copyOfOldSS, subId);
-        } catch (RuntimeException e) {
-            final String message = e.getMessage();
-            if (message != null &&  message.equals("notifyChange!")) {
-                notifyChangeWasCalled = true;
-            }
-        }
-        assertFalse(notifyChangeWasCalled);
-
         // Test that notifyChange is called by notifyChangeForSubIdAndField when the voice_reg_state
         // changes
-        notifyChangeWasCalled = false;
-        try {
-            ServiceStateProvider.notifyChangeForSubIdAndField(mContext, oldSS, newSS, subId);
-        } catch (RuntimeException e) {
-            final String message = e.getMessage();
-            if (message != null &&  message.equals("notifyChange!")) {
-                notifyChangeWasCalled = true;
-            }
-        }
-        assertTrue(notifyChangeWasCalled);
+        assertTrue(notifyChangeCalledForSubId(oldSS, newSS, subId));
 
         // Test that notifyChange is called by notifyChangeForSubId when the voice_reg_state changes
-        notifyChangeWasCalled = false;
+        assertTrue(notifyChangeCalledForSubIdAndField(oldSS, newSS, subId));
+    }
+
+    @Test
+    @SmallTest
+    public void testNotifyChanged_dataNetworkTypeUpdated() {
+        int subId = 0;
+
+        // While we don't have a method to directly set dataNetworkType, we emulate a ServiceState
+        // change that will trigger the change of dataNetworkType, according to the logic in
+        // ServiceState#getDataNetworkType
+        ServiceState oldSS = new ServiceState();
+        oldSS.setStateOutOfService();
+
+        ServiceState newSS = new ServiceState();
+        newSS.setStateOutOfService();
+
+        NetworkRegistrationInfo nriWwan = new NetworkRegistrationInfo.Builder()
+                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                .setRegistrationState(REGISTRATION_STATE_HOME)
+                .build();
+        newSS.addNetworkRegistrationInfo(nriWwan);
+
+        // Test that notifyChange is called by notifyChangeForSubId when the
+        // data_network_type changes
+        assertTrue(notifyChangeCalledForSubId(oldSS, newSS, subId));
+
+        // Test that notifyChange is called by notifyChangeForSubIdAndField when the
+        // data_network_type changes
+        assertTrue(notifyChangeCalledForSubIdAndField(oldSS, newSS, subId));
+    }
+
+    // Check if notifyChange was called by notifyChangeForSubId
+    private boolean notifyChangeCalledForSubId(ServiceState oldSS,
+            ServiceState newSS, int subId) {
         try {
             ServiceStateProvider.notifyChangeForSubId(mContext, oldSS, newSS, subId);
-        } catch (RuntimeException e) {
-            final String message = e.getMessage();
-            if (message != null &&  message.equals("notifyChange!")) {
-                notifyChangeWasCalled = true;
-            }
+        } catch (TestNotifierException e) {
+            return true;
         }
-        assertTrue(notifyChangeWasCalled);
+        return false;
+    }
+
+    // Check if notifyChange was called by notifyChangeForSubIdAndField
+    private boolean notifyChangeCalledForSubIdAndField(ServiceState oldSS,
+            ServiceState newSS, int subId) {
+        try {
+            ServiceStateProvider.notifyChangeForSubIdAndField(mContext, oldSS, newSS, subId);
+        } catch (TestNotifierException e) {
+            return true;
+        }
+        return false;
     }
 }