Add API in SmsManager to return sms messages for financial app.

Test: atest android.telephony.cts.SmsManagerTest
Bug: 111207447
Change-Id: I1571cb005f7c1374a9acbec27041bca291fa7153
diff --git a/Android.bp b/Android.bp
index d4a04cc..40e6e5f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -561,6 +561,7 @@
         "telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl",
         "telephony/java/android/telephony/mbms/vendor/IMbmsGroupCallService.aidl",
         "telephony/java/android/telephony/ICellInfoCallback.aidl",
+        "telephony/java/android/telephony/IFinancialSmsCallback.aidl",
         "telephony/java/android/telephony/INetworkService.aidl",
         "telephony/java/android/telephony/INetworkServiceCallback.aidl",
         "telephony/java/com/android/ims/internal/IImsCallSession.aidl",
diff --git a/api/current.txt b/api/current.txt
index 5093374..cfd7ce2 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -44670,6 +44670,7 @@
     method public static android.telephony.SmsManager getDefault();
     method public static int getDefaultSmsSubscriptionId();
     method public static android.telephony.SmsManager getSmsManagerForSubscriptionId(int);
+    method @RequiresPermission(android.Manifest.permission.SMS_FINANCIAL_TRANSACTIONS) public void getSmsMessagesForFinancialApp(android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.SmsManager.FinancialSmsCallback);
     method public int getSubscriptionId();
     method public void injectSmsPdu(byte[], String, android.app.PendingIntent);
     method public void sendDataMessage(String, String, short, byte[], android.app.PendingIntent, android.app.PendingIntent);
@@ -44732,6 +44733,11 @@
     field public static final int STATUS_ON_ICC_UNSENT = 7; // 0x7
   }
 
+  public abstract static class SmsManager.FinancialSmsCallback {
+    ctor public SmsManager.FinancialSmsCallback();
+    method public abstract void onFinancialSmsMessages(android.database.CursorWindow);
+  }
+
   public class SmsMessage {
     method public static int[] calculateLength(CharSequence, boolean);
     method public static int[] calculateLength(String, boolean);
diff --git a/core/java/android/app/role/IRoleManager.aidl b/core/java/android/app/role/IRoleManager.aidl
index 2964fbc..cf62e8d 100644
--- a/core/java/android/app/role/IRoleManager.aidl
+++ b/core/java/android/app/role/IRoleManager.aidl
@@ -18,6 +18,8 @@
 
 import android.app.role.IOnRoleHoldersChangedListener;
 import android.app.role.IRoleManagerCallback;
+import android.os.Bundle;
+import android.telephony.IFinancialSmsCallback;
 
 /**
  * @hide
@@ -52,4 +54,8 @@
     List<String> getHeldRolesFromController(in String packageName);
 
     String getDefaultSmsPackage(int userId);
+    /**
+     * Get filtered SMS messages for financial app.
+     */
+    void getSmsMessagesForFinancialApp(in String callingPkg, in Bundle params, in IFinancialSmsCallback callback);
 }
diff --git a/services/core/java/com/android/server/role/FinancialSmsManager.java b/services/core/java/com/android/server/role/FinancialSmsManager.java
new file mode 100644
index 0000000..2ec3993
--- /dev/null
+++ b/services/core/java/com/android/server/role/FinancialSmsManager.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2019 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.server.role;
+
+import android.Manifest;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.sms.FinancialSmsService;
+import android.service.sms.IFinancialSmsService;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * This class binds to {@code FinancialSmsService}.
+ */
+final class FinancialSmsManager {
+
+    private static final String TAG = "FinancialSmsManager";
+
+    private final Context mContext;
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private ServiceConnection mServiceConnection;
+
+    @GuardedBy("mLock")
+    private IFinancialSmsService mRemoteService;
+
+    @GuardedBy("mLock")
+    private ArrayList<Command> mQueuedCommands;
+
+    FinancialSmsManager(Context context) {
+        mContext = context;
+    }
+
+    @Nullable
+    ServiceInfo getServiceInfo() {
+        final String packageName =
+                mContext.getPackageManager().getServicesSystemSharedLibraryPackageName();
+        if (packageName == null) {
+            Slog.w(TAG, "no external services package!");
+            return null;
+        }
+
+        final Intent intent = new Intent(FinancialSmsService.ACTION_FINANCIAL_SERVICE_INTENT);
+        intent.setPackage(packageName);
+        final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
+                PackageManager.GET_SERVICES);
+        if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+            Slog.w(TAG, "No valid components found.");
+            return null;
+        }
+        return resolveInfo.serviceInfo;
+    }
+
+    @Nullable
+    private ComponentName getServiceComponentName() {
+        final ServiceInfo serviceInfo = getServiceInfo();
+        if (serviceInfo == null) return null;
+
+        final ComponentName name = new ComponentName(serviceInfo.packageName, serviceInfo.name);
+        if (!Manifest.permission.BIND_FINANCIAL_SMS_SERVICE.equals(serviceInfo.permission)) {
+            Slog.w(TAG, name.flattenToShortString() + " does not require permission "
+                    + Manifest.permission.BIND_FINANCIAL_SMS_SERVICE);
+            return null;
+        }
+
+        return name;
+    }
+
+    void reset() {
+        synchronized (mLock) {
+            if (mServiceConnection != null) {
+                mContext.unbindService(mServiceConnection);
+                mServiceConnection = null;
+            } else {
+                Slog.d(TAG, "reset(): service is not bound. Do nothing.");
+            }
+        }
+    }
+
+    /**
+     * Run a command, starting the service connection if necessary.
+     */
+    private void connectAndRun(@NonNull Command command) {
+        synchronized (mLock) {
+            if (mRemoteService != null) {
+                try {
+                    command.run(mRemoteService);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "exception calling service: " + e);
+                }
+                return;
+            } else {
+                if (mQueuedCommands == null) {
+                    mQueuedCommands = new ArrayList<>(1);
+                }
+                mQueuedCommands.add(command);
+                // If we're already connected, don't create a new connection, just leave - the
+                // command will be run when the service connects
+                if (mServiceConnection != null) return;
+            }
+
+            // Create the connection
+            mServiceConnection = new ServiceConnection() {
+                @Override
+                public void onServiceConnected(ComponentName name, IBinder service) {
+                    synchronized (mLock) {
+                        mRemoteService = IFinancialSmsService.Stub.asInterface(service);
+                        if (mQueuedCommands != null) {
+                            final int size = mQueuedCommands.size();
+                            for (int i = 0; i < size; i++) {
+                                final Command queuedCommand = mQueuedCommands.get(i);
+                                try {
+                                    queuedCommand.run(mRemoteService);
+                                } catch (RemoteException e) {
+                                    Slog.w(TAG, "exception calling " + name + ": " + e);
+                                }
+                            }
+                            mQueuedCommands = null;
+                        }
+                    }
+                }
+
+                @Override
+                @MainThread
+                public void onServiceDisconnected(ComponentName name) {
+                    synchronized (mLock) {
+                        mRemoteService = null;
+                    }
+                }
+
+                @Override
+                public void onBindingDied(ComponentName name) {
+                    synchronized (mLock) {
+                        mRemoteService = null;
+                    }
+                }
+
+                @Override
+                public void onNullBinding(ComponentName name) {
+                    synchronized (mLock) {
+                        mRemoteService = null;
+                    }
+                }
+            };
+
+            final ComponentName component = getServiceComponentName();
+            if (component != null) {
+                final Intent intent = new Intent();
+                intent.setComponent(component);
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    mContext.bindServiceAsUser(intent, mServiceConnection, Context.BIND_AUTO_CREATE,
+                            UserHandle.getUserHandleForUid(UserHandle.getCallingUserId()));
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
+        }
+    }
+
+    void getSmsMessages(RemoteCallback callback, @Nullable Bundle params) {
+        connectAndRun((service) -> service.getSmsMessages(callback, params));
+    }
+
+    void dump(String prefix, PrintWriter pw) {
+        final ComponentName impl = getServiceComponentName();
+        pw.print(prefix); pw.print("User ID: "); pw.println(UserHandle.getCallingUserId());
+        pw.print(prefix); pw.print("Queued commands: ");
+        if (mQueuedCommands == null) {
+            pw.println("N/A");
+        } else {
+            pw.println(mQueuedCommands.size());
+        }
+        pw.print(prefix); pw.print("Implementation: ");
+        if (impl == null) {
+            pw.println("N/A");
+            return;
+        }
+        pw.println(impl.flattenToShortString());
+    }
+
+    private interface Command {
+        void run(IFinancialSmsService service) throws RemoteException;
+    }
+}
diff --git a/services/core/java/com/android/server/role/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java
index 1c7596b..7d8c7c9 100644
--- a/services/core/java/com/android/server/role/RoleManagerService.java
+++ b/services/core/java/com/android/server/role/RoleManagerService.java
@@ -33,16 +33,24 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.PermissionChecker;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.Signature;
+import android.database.CursorWindow;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.Handler;
+import android.os.RemoteCallback;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.os.UserHandle;
 import android.os.UserManagerInternal;
+import android.service.sms.FinancialSmsService;
+import android.telephony.IFinancialSmsCallback;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.PackageUtils;
@@ -620,5 +628,51 @@
 
             dumpOutputStream.flush();
         }
+
+        /**
+         * Get filtered SMS messages for financial app.
+         */
+        @Override
+        public void getSmsMessagesForFinancialApp(
+                String callingPkg, Bundle params, IFinancialSmsCallback callback) {
+            int mode = PermissionChecker.checkCallingOrSelfPermission(
+                    getContext(),
+                    AppOpsManager.OPSTR_SMS_FINANCIAL_TRANSACTIONS);
+
+            if (mode == PermissionChecker.PERMISSION_GRANTED) {
+                FinancialSmsManager financialSmsManager = new FinancialSmsManager(getContext());
+                financialSmsManager.getSmsMessages(new RemoteCallback((result) -> {
+                    CursorWindow messages = null;
+                    if (result == null) {
+                        Slog.w(LOG_TAG, "result is null.");
+                    } else {
+                        messages = result.getParcelable(FinancialSmsService.EXTRA_SMS_MSGS);
+                    }
+                    try {
+                        callback.onGetSmsMessagesForFinancialApp(messages);
+                    } catch (RemoteException e) {
+                        // do nothing
+                    }
+                }), params);
+            } else {
+                try {
+                    callback.onGetSmsMessagesForFinancialApp(null);
+                } catch (RemoteException e) {
+                    // do nothing
+                }
+            }
+        }
+
+        private int getUidForPackage(String packageName) {
+            long ident = Binder.clearCallingIdentity();
+            try {
+                return getContext().getPackageManager().getApplicationInfo(packageName,
+                        PackageManager.MATCH_ANY_USER).uid;
+            } catch (NameNotFoundException nnfe) {
+                return -1;
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
     }
 }
diff --git a/telephony/java/android/telephony/IFinancialSmsCallback.aidl b/telephony/java/android/telephony/IFinancialSmsCallback.aidl
new file mode 100644
index 0000000..aa88615
--- /dev/null
+++ b/telephony/java/android/telephony/IFinancialSmsCallback.aidl
@@ -0,0 +1,34 @@
+/*
+** Copyright 2019, 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 android.telephony;
+
+import android.app.PendingIntent;
+import android.database.CursorWindow;
+import android.net.Uri;
+import android.os.Bundle;
+import com.android.internal.telephony.SmsRawData;
+
+/** Interface for returning back the financial sms messages asynchrously.
+ *  @hide
+ */
+interface IFinancialSmsCallback {
+    /**
+     * Return sms messages back to calling financial app.
+     *
+     * @param messages the sms messages returned for cinancial app.
+     */
+    oneway void onGetSmsMessagesForFinancialApp(in CursorWindow messages);
+}
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index aaca18a..fae7920 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -16,6 +16,7 @@
 
 package android.telephony;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -32,8 +33,10 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
+import android.database.CursorWindow;
 import android.net.Uri;
 import android.os.BaseBundle;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.RemoteException;
@@ -51,6 +54,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Executor;
 
 /*
  * TODO(code review): Curious question... Why are a lot of these
@@ -2146,6 +2150,43 @@
         }
     }
 
+    /** callback for providing asynchronous sms messages for financial app. */
+    public abstract static class FinancialSmsCallback {
+        /**
+         * Callback to send sms messages back to financial app asynchronously.
+         *
+         * @param msgs SMS messages.
+         */
+        public abstract void onFinancialSmsMessages(CursorWindow msgs);
+    };
+
+    /**
+     * Get SMS messages for the calling financial app.
+     * The result will be delivered asynchronously in the passing in callback interface.
+     *
+     * @param params the parameters to filter SMS messages returned.
+     * @param executor the executor on which callback will be invoked.
+     * @param callback a callback to receive CursorWindow with SMS messages.
+     */
+    @RequiresPermission(android.Manifest.permission.SMS_FINANCIAL_TRANSACTIONS)
+    public void getSmsMessagesForFinancialApp(
+            Bundle params,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull FinancialSmsCallback callback) {
+        try {
+            ISms iccSms = getISmsServiceOrThrow();
+            iccSms.getSmsMessagesForFinancialApp(
+                    getSubscriptionId(), ActivityThread.currentPackageName(), params,
+                    new IFinancialSmsCallback.Stub() {
+                        public void onGetSmsMessagesForFinancialApp(CursorWindow msgs) {
+                            Binder.withCleanCallingIdentity(() -> executor.execute(
+                                    () -> callback.onFinancialSmsMessages(msgs)));
+                        }});
+        } catch (RemoteException ex) {
+            ex.rethrowFromSystemServer();
+        }
+    }
+
     /**
      * @see #createAppSpecificSmsTokenWithPackageInfo().
      * The prefixes is a list of prefix {@code String} separated by this delimiter.
diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl
index 58ebb15..b51eda3 100644
--- a/telephony/java/com/android/internal/telephony/ISms.aidl
+++ b/telephony/java/com/android/internal/telephony/ISms.aidl
@@ -18,6 +18,8 @@
 
 import android.app.PendingIntent;
 import android.net.Uri;
+import android.os.Bundle;
+import android.telephony.IFinancialSmsCallback;
 import com.android.internal.telephony.SmsRawData;
 
 /** Interface for applications to access the ICC phone book.
@@ -575,4 +577,15 @@
      */
     String createAppSpecificSmsTokenWithPackageInfo(
             int subId, String callingPkg, String prefixes, in PendingIntent intent);
+
+    /**
+     * Get sms inbox messages for the calling financial app.
+     *
+     * @param subId the SIM id.
+     * @param callingPkg the package name of the calling app.
+     * @param params parameters to filter the sms messages.
+     * @param callback the callback interface to deliver the result.
+     */
+    void getSmsMessagesForFinancialApp(
+        int subId, String callingPkg, in Bundle params, in IFinancialSmsCallback callback);
 }
diff --git a/telephony/java/com/android/internal/telephony/ISmsImplBase.java b/telephony/java/com/android/internal/telephony/ISmsImplBase.java
index f2f2960..12c5c30 100644
--- a/telephony/java/com/android/internal/telephony/ISmsImplBase.java
+++ b/telephony/java/com/android/internal/telephony/ISmsImplBase.java
@@ -18,6 +18,8 @@
 
 import android.app.PendingIntent;
 import android.net.Uri;
+import android.os.Bundle;
+import android.telephony.IFinancialSmsCallback;
 
 import java.util.List;
 
@@ -194,4 +196,10 @@
             int subId, String callingPkg, String prefixes, PendingIntent intent) {
         throw new UnsupportedOperationException();
     }
+
+    @Override
+    public void getSmsMessagesForFinancialApp(
+            int subId, String callingPkg, Bundle params, IFinancialSmsCallback callback) {
+        throw new UnsupportedOperationException();
+    }
 }