From e921040a0261a516ba439219791409deb91827be Mon Sep 17 00:00:00 2001
From: Fahad Afroze <fafroze@google.com>
Date: Thu, 15 Dec 2022 13:17:12 +0000
Subject: [PATCH] [PTS-Bot]: Added 7 OPP/SR test cases

Added opp.py file
Updated _android.proto and AndroidInternal.kt files
Added AcceptIncomingFile function in AndroidInternal.kt using uiautomator.

Test: atest pts-bot:OPP -v
Change-Id: If2ab94f8033035ddf7b40a1a5acdc58b8f272acc
---
 android/pandora/mmi2grpc/mmi2grpc/__init__.py |   8 ++
 android/pandora/mmi2grpc/mmi2grpc/opp.py      | 118 ++++++++++++++++++
 android/pandora/server/Android.bp             |   1 +
 android/pandora/server/configs/PtsBotTest.xml |   1 +
 .../pandora/server/configs/PtsBotTestMts.xml  |   1 +
 .../server/configs/pts_bot_tests_config.json  |  16 +++
 .../com/android/pandora/AndroidInternal.kt    |  17 +++
 .../pandora_experimental/_android.proto       |   2 +
 8 files changed, 164 insertions(+)
 create mode 100644 android/pandora/mmi2grpc/mmi2grpc/opp.py

diff --git a/android/pandora/mmi2grpc/mmi2grpc/__init__.py b/android/pandora/mmi2grpc/mmi2grpc/__init__.py
index 1047e458a06..97235237736 100644
--- a/android/pandora/mmi2grpc/mmi2grpc/__init__.py
+++ b/android/pandora/mmi2grpc/mmi2grpc/__init__.py
@@ -31,6 +31,7 @@ from mmi2grpc.hid import HIDProxy
 from mmi2grpc.hogp import HOGPProxy
 from mmi2grpc.l2cap import L2CAPProxy
 from mmi2grpc.map import MAPProxy
+from mmi2grpc.opp import OPPProxy
 from mmi2grpc.pbap import PBAPProxy
 from mmi2grpc.rfcomm import RFCOMMProxy
 from mmi2grpc.sdp import SDPProxy
@@ -80,6 +81,7 @@ class IUT:
         self._hogp = None
         self._l2cap = None
         self._map = None
+        self._opp = None
         self._pbap = None
         self._rfcomm = None
         self._sdp = None
@@ -113,6 +115,7 @@ class IUT:
         self._hid = None
         self._hogp = None
         self._map = None
+        self._opp = None
         self._pbap = None
         self._rfcomm = None
         self._sdp = None
@@ -232,6 +235,11 @@ class IUT:
             if not self._map:
                 self._map = MAPProxy(grpc.insecure_channel(f"localhost:{self.pandora_server_port}"))
             return self._map.interact(test, interaction, description, pts_address)
+        # Handles OPP MMIs.
+        if profile in ("OPP"):
+            if not self._opp:
+                self._opp = OPPProxy(grpc.insecure_channel(f"localhost:{self.pandora_server_port}"))
+            return self._opp.interact(test, interaction, description, pts_address)
         # Instantiates PBAP proxy and reroutes corresponding MMIs to it.
         if profile in ("PBAP"):
             if not self._pbap:
diff --git a/android/pandora/mmi2grpc/mmi2grpc/opp.py b/android/pandora/mmi2grpc/mmi2grpc/opp.py
new file mode 100644
index 00000000000..d74e5cf7ea9
--- /dev/null
+++ b/android/pandora/mmi2grpc/mmi2grpc/opp.py
@@ -0,0 +1,118 @@
+# Copyright 2022 Google LLC
+#
+# 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
+#
+#     https://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.
+"""OPP proxy module."""
+
+from typing import Optional
+
+from mmi2grpc._helpers import assert_description
+from mmi2grpc._proxy import ProfileProxy
+
+from pandora_experimental.host_grpc import Host
+from pandora_experimental.host_pb2 import Connection
+from pandora_experimental._android_grpc import Android
+from pandora_experimental._android_pb2 import AccessType
+
+
+class OPPProxy(ProfileProxy):
+    """OPP proxy.
+
+    Implements OPP PTS MMIs.
+    """
+    connection: Optional[Connection] = None
+
+    def __init__(self, channel):
+        super().__init__(channel)
+
+        self.host = Host(channel)
+        self._android = Android(channel)
+
+        self.connection = None
+
+    @assert_description
+    def TSC_OBEX_MMI_iut_accept_connect_OPP(self, pts_addr: bytes, **kwargs):
+        """
+        Please accept the OBEX CONNECT REQ command for OPP.
+        """
+        if self.connection is None:
+            self.connection = self.host.WaitConnection(address=pts_addr).connection
+
+        return "OK"
+
+    @assert_description
+    def TSC_OPP_mmi_user_action_remove_object(self, **kwargs):
+        """
+        If necessary take action to remove any file(s) named 'BC_BV01.bmp' from
+        the IUT.  
+
+        Press 'OK' to confirm that the file is not present on the
+        IUT.
+        """
+
+        return "OK"
+
+    @assert_description
+    def TSC_OBEX_MMI_iut_accept_put(self, **kwargs):
+        """
+         Please accept the PUT REQUEST.
+        """
+        self._android.AcceptIncomingFile()
+
+        return "OK"
+
+    @assert_description
+    def TSC_OPP_mmi_user_verify_does_object_exist(self, **kwargs):
+        """
+        Does the IUT now contain the following files?
+
+        BC_BV01.bmp
+
+        Note: If
+        TSPX_supported_extension is not .bmp, the file content of the file will
+        not be formatted for the TSPX_supported extension, this is normal.
+        """
+
+        return "OK"
+
+    @assert_description
+    def TSC_OBEX_MMI_iut_accept_slc_connect_l2cap(self, pts_addr: bytes, **kwargs):
+        """
+        Please accept the l2cap channel connection for an OBEX connection.
+        """
+        self.connection = self.host.WaitConnection(address=pts_addr).connection
+
+        return "OK"
+
+    @assert_description
+    def TSC_OBEX_MMI_iut_reject_action(self, **kwargs):
+        """
+         Take action to reject the ACTION command sent by PTS.
+        """
+
+        return "OK"
+
+    @assert_description
+    def TSC_OBEX_MMI_iut_accept_disconnect(self, **kwargs):
+        """
+         Please accept the OBEX DISCONNECT REQ command.
+        """
+
+        return "OK"
+
+    @assert_description
+    def TSC_OBEX_MMI_iut_accept_slc_disconnect(self, **kwargs):
+        """
+         Please accept the disconnection of the transport channel.
+        """
+
+        return "OK"
diff --git a/android/pandora/server/Android.bp b/android/pandora/server/Android.bp
index 6598be3dd37..7eb0eeb0100 100644
--- a/android/pandora/server/Android.bp
+++ b/android/pandora/server/Android.bp
@@ -18,6 +18,7 @@ java_library_static {
     static_libs: [
         "androidx.test.runner",
         "androidx.test.core",
+        "androidx.test.uiautomator_uiautomator",
         "grpc-java-netty-shaded-test",
         "grpc-java-lite",
         "guava",
diff --git a/android/pandora/server/configs/PtsBotTest.xml b/android/pandora/server/configs/PtsBotTest.xml
index b14f0ca9264..feb852421ee 100644
--- a/android/pandora/server/configs/PtsBotTest.xml
+++ b/android/pandora/server/configs/PtsBotTest.xml
@@ -43,6 +43,7 @@
         <option name="profile" value="L2CAP/COS" />
         <option name="profile" value="L2CAP/LE" />
         <option name="profile" value="MAP" />
+        <option name="profile" value="OPP" />
         <option name="profile" value="PBAP/PSE" />
         <option name="profile" value="RFCOMM" />
         <option name="profile" value="SDP" />
diff --git a/android/pandora/server/configs/PtsBotTestMts.xml b/android/pandora/server/configs/PtsBotTestMts.xml
index 179dda913cd..5df935cee22 100644
--- a/android/pandora/server/configs/PtsBotTestMts.xml
+++ b/android/pandora/server/configs/PtsBotTestMts.xml
@@ -43,6 +43,7 @@
         <option name="profile" value="L2CAP/COS" />
         <option name="profile" value="L2CAP/LE" />
         <option name="profile" value="MAP" />
+        <option name="profile" value="OPP" />
         <option name="profile" value="PBAP/PSE" />
         <option name="profile" value="RFCOMM" />
         <option name="profile" value="SDP" />
diff --git a/android/pandora/server/configs/pts_bot_tests_config.json b/android/pandora/server/configs/pts_bot_tests_config.json
index 69098c9583e..fdf6cad76c1 100644
--- a/android/pandora/server/configs/pts_bot_tests_config.json
+++ b/android/pandora/server/configs/pts_bot_tests_config.json
@@ -375,6 +375,13 @@
     "MAP/MSE/MSM/BV-06-I",
     "MAP/MSE/MSM/BV-07-I",
     "MAP/MSE/MSM/BV-08-I",
+    "OPP/SR/GOEP/BC/BV-01-I",
+    "OPP/SR/GOEP/CON/BV-02-C",
+    "OPP/SR/GOEP/ROB/BV-01-C",
+    "OPP/SR/GOEP/SRM/BI-03-C",
+    "OPP/SR/OPH/BV-01-I",
+    "OPP/SR/OPH/BV-02-I",
+    "OPP/SR/OPH/BV-34-I",
     "PBAP/PSE/GOEP/BC/BV-03-I",
     "PBAP/PSE/GOEP/CON/BV-02-C",
     "PBAP/PSE/GOEP/ROB/BV-01-C",
@@ -765,6 +772,15 @@
     "MAP/MSE/MMN/BV-14-I",
     "MAP/MSE/MMU/BV-02-I",
     "PBAP/PSE/SSM/BV-07-C",
+    "OPP/CL/GOEP/BC/BV-02-I",
+    "OPP/CL/GOEP/CON/BV-01-C",
+    "OPP/CL/OPH/BV-01-I",
+    "OPP/CL/OPH/BV-34-I",
+    "OPP/SR/BCP/BV-02-I",
+    "OPP/SR/GOEP/ROB/BV-02-C",
+    "OPP/SR/OPH/BV-10-I",
+    "OPP/SR/OPH/BV-14-I",
+    "OPP/SR/OPH/BV-18-I",
     "RFCOMM/DEVA-DEVB/RFC/BV-21-C",
     "RFCOMM/DEVA-DEVB/RFC/BV-22-C",
     "SM/CEN/PKE/BV-01-C",
diff --git a/android/pandora/server/src/com/android/pandora/AndroidInternal.kt b/android/pandora/server/src/com/android/pandora/AndroidInternal.kt
index 21b73d2897f..9b70fb00a2c 100644
--- a/android/pandora/server/src/com/android/pandora/AndroidInternal.kt
+++ b/android/pandora/server/src/com/android/pandora/AndroidInternal.kt
@@ -28,6 +28,10 @@ import android.net.Uri
 import android.bluetooth.BluetoothAdapter
 import android.bluetooth.BluetoothDevice
 import android.bluetooth.BluetoothManager
+import androidx.test.InstrumentationRegistry
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.cancel
@@ -40,10 +44,15 @@ private const val TAG = "PandoraAndroidInternal"
 class AndroidInternal(val context: Context) : AndroidImplBase() {
 
   private val scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
+  private val INCOMING_FILE_ACCEPT_BTN = "ACCEPT"
+  private val INCOMING_FILE_TITLE = "Incoming file"
+  private val INCOMING_FILE_WAIT_TIMEOUT = 2000L
+
   private val bluetoothManager = context.getSystemService(BluetoothManager::class.java)!!
   private val bluetoothAdapter = bluetoothManager.adapter
   private var telephonyManager = context.getSystemService(TelephonyManager::class.java)
   private val DEFAULT_MESSAGE_LEN = 130
+  private var device: UiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
 
   fun deinit() {
     scope.cancel()
@@ -80,4 +89,12 @@ class AndroidInternal(val context: Context) : AndroidImplBase() {
       Empty.getDefaultInstance()
     }
   }
+
+  override fun acceptIncomingFile(request: Empty, responseObserver: StreamObserver<Empty>) {
+    grpcUnary<Empty>(scope, responseObserver) {
+      device.wait(Until.findObject(By.text(INCOMING_FILE_TITLE)), INCOMING_FILE_WAIT_TIMEOUT).click()
+      device.wait(Until.findObject(By.text(INCOMING_FILE_ACCEPT_BTN)), INCOMING_FILE_WAIT_TIMEOUT).click()
+      Empty.getDefaultInstance()
+    }
+  }
 }
diff --git a/pandora/interfaces/pandora_experimental/_android.proto b/pandora/interfaces/pandora_experimental/_android.proto
index 27960fc17f7..cda445ea84a 100644
--- a/pandora/interfaces/pandora_experimental/_android.proto
+++ b/pandora/interfaces/pandora_experimental/_android.proto
@@ -18,6 +18,8 @@ service Android {
   rpc SetAccessPermission(SetAccessPermissionRequest) returns (google.protobuf.Empty);
   // Send SMS
   rpc SendSMS(google.protobuf.Empty) returns (google.protobuf.Empty);
+  // Accept incoming file
+  rpc AcceptIncomingFile(google.protobuf.Empty) returns (google.protobuf.Empty);
 }
 
 message LogRequest {
-- 
GitLab