TelephonyManager Carrier Network Change Notification

Adds a way for a carrier app to notify the system that an intended network
change is starting or ending. This can be used by a system PhoneStateListener
to provide custom UI or perform other actions during this period.

- Adds new public TelephonyManager API: notifyCarrierNetworkChange(boolean)
- Adds new @hide PhoneStateListener method: onCarrierNetworkChange(boolean)
- Functionality merely serves as a pass-through of data from an app to a
  PhoneStateListener (SystemUI for the intended use case)
- Protected by MODIFY_PHONE_STATE permission or hasCarrierPrivileges().

Bug: 11392659

Change-Id: I3199e21ec1ac124198f44b86c1534dd3ff1f6858
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index d153233..908ee22 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -181,6 +181,8 @@
 
     private PreciseCallState mPreciseCallState = new PreciseCallState();
 
+    private boolean mCarrierNetworkChangeState = false;
+
     private PreciseDataConnectionState mPreciseDataConnectionState =
                 new PreciseDataConnectionState();
 
@@ -607,6 +609,13 @@
                             remove(r.binder);
                         }
                     }
+                    if ((events & PhoneStateListener.LISTEN_CARRIER_NETWORK_CHANGE) != 0) {
+                        try {
+                            r.callback.onCarrierNetworkChange(mCarrierNetworkChangeState);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
+                    }
                 }
             }
         } else {
@@ -790,6 +799,31 @@
         broadcastSignalStrengthChanged(signalStrength, subId);
     }
 
+    @Override
+    public void notifyCarrierNetworkChange(boolean active) {
+        if (!checkNotifyPermissionOrCarrierPrivilege("notifyCarrierNetworkChange()")) {
+            return;
+        }
+        if (VDBG) {
+            log("notifyCarrierNetworkChange: active=" + active);
+        }
+
+        synchronized (mRecords) {
+            mCarrierNetworkChangeState = active;
+            for (Record r : mRecords) {
+                if (r.matchPhoneStateListenerEvent(
+                        PhoneStateListener.LISTEN_CARRIER_NETWORK_CHANGE)) {
+                    try {
+                        r.callback.onCarrierNetworkChange(active);
+                    } catch (RemoteException ex) {
+                        mRemoveList.add(r.binder);
+                    }
+                }
+            }
+            handleRemoveListLocked();
+        }
+    }
+
     public void notifyCellInfo(List<CellInfo> cellInfo) {
          notifyCellInfoForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, cellInfo);
     }
@@ -1422,9 +1456,19 @@
                 android.Manifest.permission.READ_PRECISE_PHONE_STATE);
     }
 
+    private boolean checkNotifyPermissionOrCarrierPrivilege(String method) {
+        if  (checkNotifyPermission() || checkCarrierPrivilege()) {
+            return true;
+        }
+
+        String msg = "Modify Phone State or Carrier Privilege Permission Denial: " + method
+                + " from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid();
+        if (DBG) log(msg);
+        return false;
+    }
+
     private boolean checkNotifyPermission(String method) {
-        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-                == PackageManager.PERMISSION_GRANTED) {
+        if (checkNotifyPermission()) {
             return true;
         }
         String msg = "Modify Phone State Permission Denial: " + method + " from pid="
@@ -1433,6 +1477,24 @@
         return false;
     }
 
+    private boolean checkNotifyPermission() {
+        return mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    private boolean checkCarrierPrivilege() {
+        TelephonyManager tm = TelephonyManager.getDefault();
+        String[] pkgs = mContext.getPackageManager().getPackagesForUid(Binder.getCallingUid());
+        for (String pkg : pkgs) {
+            if (tm.checkCarrierPrivilegesForPackage(pkg) ==
+                    TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
     private void checkListenerPermission(int events) {
         if ((events & PhoneStateListener.LISTEN_CELL_LOCATION) != 0) {
             mContext.enforceCallingOrSelfPermission(