Merge "Add API to listen to opportunistic subscriptions change."
diff --git a/api/current.txt b/api/current.txt
index eed925d..fe8ee50 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -42440,6 +42440,7 @@
   }
 
   public class SubscriptionManager {
+    method public void addOnOpportunisticSubscriptionsChangedListener(java.util.concurrent.Executor, android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener);
     method public void addOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
     method public boolean canManageSubscription(android.telephony.SubscriptionInfo);
     method public static deprecated android.telephony.SubscriptionManager from(android.content.Context);
@@ -42456,6 +42457,7 @@
     method public static int[] getSubscriptionIds(int);
     method public java.util.List<android.telephony.SubscriptionPlan> getSubscriptionPlans(int);
     method public boolean isNetworkRoaming(int);
+    method public void removeOnOpportunisticSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener);
     method public void removeOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
     method public void setSubscriptionOverrideCongested(int, boolean, long);
     method public void setSubscriptionOverrideUnmetered(int, boolean, long);
@@ -42471,6 +42473,11 @@
     field public static final int INVALID_SUBSCRIPTION_ID = -1; // 0xffffffff
   }
 
+  public static class SubscriptionManager.OnOpportunisticSubscriptionsChangedListener {
+    ctor public SubscriptionManager.OnOpportunisticSubscriptionsChangedListener();
+    method public void onOpportunisticSubscriptionsChanged();
+  }
+
   public static class SubscriptionManager.OnSubscriptionsChangedListener {
     ctor public SubscriptionManager.OnSubscriptionsChangedListener();
     method public void onSubscriptionsChanged();
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 566ce4f..0955bed 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -99,6 +99,7 @@
 
         IPhoneStateListener callback;
         IOnSubscriptionsChangedListener onSubscriptionsChangedListenerCallback;
+        IOnSubscriptionsChangedListener onOpportunisticSubscriptionsChangedListenerCallback;
 
         int callerUid;
         int callerPid;
@@ -117,6 +118,10 @@
             return (onSubscriptionsChangedListenerCallback != null);
         }
 
+        boolean matchOnOpportunisticSubscriptionsChangedListener() {
+            return (onOpportunisticSubscriptionsChangedListenerCallback != null);
+        }
+
         boolean canReadCallLog() {
             try {
                 return TelephonyPermissions.checkReadCallLog(
@@ -131,7 +136,9 @@
             return "{callingPackage=" + callingPackage + " binder=" + binder
                     + " callback=" + callback
                     + " onSubscriptionsChangedListenererCallback="
-                                            + onSubscriptionsChangedListenerCallback
+                    + onSubscriptionsChangedListenerCallback
+                    + " onOpportunisticSubscriptionsChangedListenererCallback="
+                    + onOpportunisticSubscriptionsChangedListenerCallback
                     + " callerUid=" + callerUid + " subId=" + subId + " phoneId=" + phoneId
                     + " events=" + Integer.toHexString(events) + "}";
         }
@@ -147,7 +154,9 @@
 
     private final AppOpsManager mAppOps;
 
-    private boolean hasNotifySubscriptionInfoChangedOccurred = false;
+    private boolean mHasNotifySubscriptionInfoChangedOccurred = false;
+
+    private boolean mHasNotifyOpportunisticSubscriptionInfoChangedOccurred = false;
 
     private int mNumPhones;
 
@@ -407,7 +416,7 @@
                 log("listen oscl:  Register r=" + r);
             }
             // Always notify when registration occurs if there has been a notification.
-            if (hasNotifySubscriptionInfoChangedOccurred) {
+            if (mHasNotifySubscriptionInfoChangedOccurred) {
                 try {
                     if (VDBG) log("listen oscl: send to r=" + r);
                     r.onSubscriptionsChangedListenerCallback.onSubscriptionsChanged();
@@ -417,7 +426,7 @@
                     remove(r.binder);
                 }
             } else {
-                log("listen oscl: hasNotifySubscriptionInfoChangedOccurred==false no callback");
+                log("listen oscl: mHasNotifySubscriptionInfoChangedOccurred==false no callback");
             }
         }
     }
@@ -429,15 +438,61 @@
         remove(callback.asBinder());
     }
 
+
+    @Override
+    public void addOnOpportunisticSubscriptionsChangedListener(String callingPackage,
+            IOnSubscriptionsChangedListener callback) {
+        int callerUserId = UserHandle.getCallingUserId();
+        mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
+        if (VDBG) {
+            log("listen ooscl: E pkg=" + callingPackage + " myUserId=" + UserHandle.myUserId()
+                    + " callerUserId="  + callerUserId + " callback=" + callback
+                    + " callback.asBinder=" + callback.asBinder());
+        }
+
+        synchronized (mRecords) {
+            // register
+            IBinder b = callback.asBinder();
+            Record r = add(b);
+
+            if (r == null) {
+                return;
+            }
+
+            r.context = mContext;
+            r.onOpportunisticSubscriptionsChangedListenerCallback = callback;
+            r.callingPackage = callingPackage;
+            r.callerUid = Binder.getCallingUid();
+            r.callerPid = Binder.getCallingPid();
+            r.events = 0;
+            if (DBG) {
+                log("listen ooscl:  Register r=" + r);
+            }
+            // Always notify when registration occurs if there has been a notification.
+            if (mHasNotifyOpportunisticSubscriptionInfoChangedOccurred) {
+                try {
+                    if (VDBG) log("listen ooscl: send to r=" + r);
+                    r.onOpportunisticSubscriptionsChangedListenerCallback.onSubscriptionsChanged();
+                    if (VDBG) log("listen ooscl: sent to r=" + r);
+                } catch (RemoteException e) {
+                    if (VDBG) log("listen ooscl: remote exception sending to r=" + r + " e=" + e);
+                    remove(r.binder);
+                }
+            } else {
+                log("listen ooscl: hasNotifyOpptSubInfoChangedOccurred==false no callback");
+            }
+        }
+    }
+
     @Override
     public void notifySubscriptionInfoChanged() {
         if (VDBG) log("notifySubscriptionInfoChanged:");
         synchronized (mRecords) {
-            if (!hasNotifySubscriptionInfoChangedOccurred) {
+            if (!mHasNotifySubscriptionInfoChangedOccurred) {
                 log("notifySubscriptionInfoChanged: first invocation mRecords.size="
                         + mRecords.size());
             }
-            hasNotifySubscriptionInfoChangedOccurred = true;
+            mHasNotifySubscriptionInfoChangedOccurred = true;
             mRemoveList.clear();
             for (Record r : mRecords) {
                 if (r.matchOnSubscriptionsChangedListener()) {
@@ -456,6 +511,33 @@
     }
 
     @Override
+    public void notifyOpportunisticSubscriptionInfoChanged() {
+        if (VDBG) log("notifyOpptSubscriptionInfoChanged:");
+        synchronized (mRecords) {
+            if (!mHasNotifyOpportunisticSubscriptionInfoChangedOccurred) {
+                log("notifyOpptSubscriptionInfoChanged: first invocation mRecords.size="
+                        + mRecords.size());
+            }
+            mHasNotifyOpportunisticSubscriptionInfoChangedOccurred = true;
+            mRemoveList.clear();
+            for (Record r : mRecords) {
+                if (r.matchOnOpportunisticSubscriptionsChangedListener()) {
+                    try {
+                        if (VDBG) log("notifyOpptSubChanged: call oosc to r=" + r);
+                        r.onOpportunisticSubscriptionsChangedListenerCallback
+                                .onSubscriptionsChanged();
+                        if (VDBG) log("notifyOpptSubChanged: done oosc to r=" + r);
+                    } catch (RemoteException ex) {
+                        if (VDBG) log("notifyOpptSubChanged: RemoteException r=" + r);
+                        mRemoveList.add(r.binder);
+                    }
+                }
+            }
+            handleRemoveListLocked();
+        }
+    }
+
+    @Override
     public void listen(String pkgForDebug, IPhoneStateListener callback, int events,
             boolean notifyNow) {
         listenForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, pkgForDebug, callback,
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index d78f492..f7c8e0e 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -19,6 +19,7 @@
 import static android.net.NetworkPolicyManager.OVERRIDE_CONGESTED;
 import static android.net.NetworkPolicyManager.OVERRIDE_UNMETERED;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.DurationMillisLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -47,6 +48,7 @@
 import android.os.ServiceManager;
 import android.telephony.euicc.EuiccManager;
 import android.util.DisplayMetrics;
+import android.util.Log;
 
 import com.android.internal.telephony.IOnSubscriptionsChangedListener;
 import com.android.internal.telephony.ISub;
@@ -57,6 +59,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -666,7 +669,7 @@
                 tr.addOnSubscriptionsChangedListener(pkgName, listener.callback);
             }
         } catch (RemoteException ex) {
-            // Should not happen
+            Log.e(LOG_TAG, "Remote exception ITelephonyRegistry " + ex);
         }
     }
 
@@ -684,7 +687,7 @@
                     + " listener=" + listener);
         }
         try {
-            // We use the TelephonyRegistry as its runs in the system and thus is always
+            // We use the TelephonyRegistry as it runs in the system and thus is always
             // available where as SubscriptionController could crash and not be available
             ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
                     "telephony.registry"));
@@ -692,7 +695,116 @@
                 tr.removeOnSubscriptionsChangedListener(pkgForDebug, listener.callback);
             }
         } catch (RemoteException ex) {
-            // Should not happen
+            Log.e(LOG_TAG, "Remote exception ITelephonyRegistry " + ex);
+        }
+    }
+
+    /**
+     * A listener class for monitoring changes to {@link SubscriptionInfo} records of opportunistic
+     * subscriptions.
+     * <p>
+     * Override the onOpportunisticSubscriptionsChanged method in the object that extends this
+     * or {@link #addOnOpportunisticSubscriptionsChangedListener(
+     * Executor, OnOpportunisticSubscriptionsChangedListener)}
+     * to register your listener and to unregister invoke
+     * {@link #removeOnOpportunisticSubscriptionsChangedListener(
+     * OnOpportunisticSubscriptionsChangedListener)}
+     * <p>
+     * Permissions android.Manifest.permission.READ_PHONE_STATE is required
+     * for #onOpportunisticSubscriptionsChanged to be invoked.
+     */
+    public static class OnOpportunisticSubscriptionsChangedListener {
+        private Executor mExecutor;
+        /**
+         * Callback invoked when there is any change to any SubscriptionInfo. Typically
+         * this method would invoke {@link #getActiveSubscriptionInfoList}
+         */
+        public void onOpportunisticSubscriptionsChanged() {
+            if (DBG) log("onOpportunisticSubscriptionsChanged: NOT OVERRIDDEN");
+        }
+
+        private void setExecutor(Executor executor) {
+            mExecutor = executor;
+        }
+
+        /**
+         * The callback methods need to be called on the handler thread where
+         * this object was created.  If the binder did that for us it'd be nice.
+         */
+        IOnSubscriptionsChangedListener callback = new IOnSubscriptionsChangedListener.Stub() {
+            @Override
+            public void onSubscriptionsChanged() {
+                if (DBG) log("onOpportunisticSubscriptionsChanged callback received.");
+                mExecutor.execute(() -> onOpportunisticSubscriptionsChanged());
+            }
+        };
+
+        private void log(String s) {
+            Rlog.d(LOG_TAG, s);
+        }
+    }
+
+    /**
+     * Register for changes to the list of opportunistic subscription records or to the
+     * individual records themselves. When a change occurs the onOpportunisticSubscriptionsChanged
+     * method of the listener will be invoked immediately if there has been a notification.
+     *
+     * @param listener an instance of {@link OnOpportunisticSubscriptionsChangedListener} with
+     *                 onOpportunisticSubscriptionsChanged overridden.
+     */
+    public void addOnOpportunisticSubscriptionsChangedListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnOpportunisticSubscriptionsChangedListener listener) {
+        if (executor == null || listener == null) {
+            return;
+        }
+
+        String pkgName = mContext != null ? mContext.getOpPackageName() : "<unknown>";
+        if (DBG) {
+            logd("register addOnOpportunisticSubscriptionsChangedListener pkgName=" + pkgName
+                    + " listener=" + listener);
+        }
+
+        listener.setExecutor(executor);
+
+        try {
+            // We use the TelephonyRegistry as it runs in the system and thus is always
+            // available. Where as SubscriptionController could crash and not be available
+            ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
+                    "telephony.registry"));
+            if (tr != null) {
+                tr.addOnOpportunisticSubscriptionsChangedListener(pkgName, listener.callback);
+            }
+        } catch (RemoteException ex) {
+            Log.e(LOG_TAG, "Remote exception ITelephonyRegistry " + ex);
+        }
+    }
+
+    /**
+     * Unregister the {@link OnOpportunisticSubscriptionsChangedListener} that is currently
+     * listening opportunistic subscriptions change. This is not strictly necessary
+     * as the listener will automatically be unregistered if an attempt to invoke the listener
+     * fails.
+     *
+     * @param listener that is to be unregistered.
+     */
+    public void removeOnOpportunisticSubscriptionsChangedListener(
+            OnOpportunisticSubscriptionsChangedListener listener) {
+        String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>";
+        if (DBG) {
+            logd("unregister OnOpportunisticSubscriptionsChangedListener pkgForDebug="
+                    + pkgForDebug + " listener=" + listener);
+        }
+        try {
+            // We use the TelephonyRegistry as it runs in the system and thus is always
+            // available where as SubscriptionController could crash and not be available
+            ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
+                    "telephony.registry"));
+            if (tr != null) {
+                tr.removeOnSubscriptionsChangedListener(pkgForDebug, listener.callback);
+            }
+        } catch (RemoteException ex) {
+            Log.e(LOG_TAG, "Remote exception ITelephonyRegistry " + ex);
         }
     }
 
diff --git a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index e0e1a7b..43d56b3 100644
--- a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -32,6 +32,8 @@
 interface ITelephonyRegistry {
     void addOnSubscriptionsChangedListener(String pkg,
             IOnSubscriptionsChangedListener callback);
+    void addOnOpportunisticSubscriptionsChangedListener(String pkg,
+            IOnSubscriptionsChangedListener callback);
     void removeOnSubscriptionsChangedListener(String pkg,
             IOnSubscriptionsChangedListener callback);
     void listen(String pkg, IPhoneStateListener callback, int events, boolean notifyNow);
@@ -73,6 +75,7 @@
             int activationState, int activationType);
     void notifyOemHookRawEventForSubscriber(in int subId, in byte[] rawData);
     void notifySubscriptionInfoChanged();
+    void notifyOpportunisticSubscriptionInfoChanged();
     void notifyCarrierNetworkChange(in boolean active);
     void notifyUserMobileDataStateChangedForPhoneId(in int phoneId, in int subId, in boolean state);
     void notifyPhoneCapabilityChanged(in PhoneCapability capability);