| #!/usr/bin/env python3 |
| |
| import os |
| import time |
| from uiautomator import Device |
| import sys |
| import subprocess |
| import pathlib |
| |
| |
| PREBUILTS_PATH = '../../vendor/smartviser/viser/prebuilts/apk' |
| |
| PREBUILT_PROXY_APKS = { |
| 'gms': 'com.lunarlabs.panda.proxy.apk', |
| 'sibon': 'com.lunarlabs.panda.proxy-sibon.apk', |
| } |
| |
| PREBUILT_APKS = [ |
| 'com.smartviser.demogame.apk', |
| 'com.lunarlabs.panda.apk', |
| ] |
| |
| |
| def adb(*args, serial = None): |
| """Run ADB command attached to serial. |
| |
| Example: |
| >>> process = adb('shell', 'getprop', 'ro.build.fingerprint', serial='cc60c021') |
| >>> process.returncode |
| 0 |
| >>> process.stdout.strip() |
| 'Fairphone/FP2/FP2:6.0.1/FP2-gms-18.02.0/FP2-gms-18.02.0:user/release-keys' |
| |
| :param *args: |
| List of options to ADB (including command). |
| :param str serial: |
| Identifier for ADB connection to device. |
| :returns subprocess.CompletedProcess: |
| Completed process. |
| """ |
| |
| # Make sure the adb server is started to avoid the infamous "out of date" |
| # message that pollutes stdout. |
| subprocess.run( |
| ['adb', 'start-server'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, |
| universal_newlines=True) |
| |
| command = ['adb'] |
| if serial: |
| command += ['-s', serial] |
| if args: |
| command += list(args) |
| return subprocess.run( |
| command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, |
| universal_newlines=True) |
| |
| |
| def aapt(*args): |
| """Run an AAPT command. |
| |
| :param *args: |
| The AAPT command with its options. |
| :returns subprocess.CompletedProcess: |
| Completed process. |
| """ |
| command = ['aapt'] |
| if args: |
| command += list(args) |
| return subprocess.run( |
| command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, |
| universal_newlines=True) |
| |
| |
| def force_awake(serial, always=True): |
| """Force the device to stay awake.""" |
| return adb('shell', 'svc power stayon {}'.format( |
| 'true' if always else 'false'), serial=serial) |
| |
| |
| def unlock(serial): |
| """Wake-up the device and unlock it.""" |
| adb('shell', 'input keyevent KEYCODE_POWER', serial=serial) |
| time.sleep(1) |
| adb('shell', 'input keyevent KEYCODE_MENU', serial=serial) |
| time.sleep(1) |
| adb('shell', 'input keyevent KEYCODE_HOME', serial=serial) |
| |
| |
| def getprop(serial, key): |
| """Get system property of device. |
| |
| Example: |
| >>> getprop('167eb6e8', 'ro.build.id') |
| 'FP2-gms-18.02.0' |
| |
| :param str serial: |
| Identifier for ADB connection to device. |
| :param str key: |
| Key of property to get. |
| :returns str: |
| Value of system property. |
| """ |
| process = adb('shell', 'getprop', key, serial=serial) |
| return process.stdout.strip() |
| |
| |
| def is_gms_device(serial): |
| """Test if device runs GMS or sibon. |
| |
| Example: |
| >>> is_gms_device('167eb6e8') |
| True |
| |
| :param str serial: |
| Identifier for ADB connection to device. |
| :returns bool: |
| True if device runs GMS, false otherwise. |
| """ |
| return getprop(serial, 'ro.build.id').startswith('FP2-gms-') |
| |
| |
| def uninstall_apk(serial, filename, prebuilts_dir): |
| """Uninstall apk from prebuilts_dir on device.""" |
| ret = aapt('dump', 'badging', '{}/{}'.format(prebuilts_dir, filename)) |
| if 0 < ret.returncode: |
| print('Could retrieve app `{}` info, error was: {}'.format( |
| filename, ret.stderr), file=sys.stderr) |
| else: |
| package = None |
| for line in ret.stdout.splitlines(): |
| if line.startswith('package'): |
| for token in line.split(' '): |
| if token.startswith('name='): |
| # Extract the package name out of the token |
| # (name='some.package.name') |
| package = token[6:-1] |
| break |
| if not package: |
| print('Could not find package of app `{}`'.format(filename), |
| file=sys.stderr) |
| else: |
| print('Uninstalling `{}`'.format(package)) |
| ret = adb('uninstall', package, serial=serial) |
| if 0 < ret.returncode: |
| print('Could not uninstall app `{}`, error was: {}'.format( |
| filename, ret.stderr), file=sys.stderr) |
| else: |
| print('App `{}` uninstalled.'.format(filename)) |
| |
| |
| def install_apk(serial, filename, prebuilts_dir): |
| """Install apk from prebuilts_dir on device.""" |
| print('Installing {}.'.format(filename)) |
| path = os.path.join(prebuilts_dir, filename) |
| ret = adb('install', '-r', path, serial=serial) |
| if 0 < ret.returncode: |
| print('Could not install app `{}`, error was: {}'.format( |
| filename, ret.stderr), file=sys.stderr) |
| else: |
| print('App `{}` installed.'.format(filename)) |
| |
| |
| # Prepare the DUT |
| def prepare_dut(serial, scenarios_dir, data_dir, prebuilts_dir): |
| flavour = 'gms' if is_gms_device(serial) else 'sibon' |
| |
| # Uninstall the smartviser apps |
| for app in PREBUILT_APKS + [PREBUILT_PROXY_APKS[flavour]]: |
| uninstall_apk(serial, app, prebuilts_dir) |
| |
| # Copy the scenarios |
| ret = adb('push', scenarios_dir, '/sdcard/viser', serial=serial) |
| if 0 < ret.returncode: |
| print('Could not push the scenarios, error was: {}'.format(ret.stderr), |
| file=sys.stderr) |
| else: |
| print('Scenarios pushed.') |
| |
| # Copy the scenarios data |
| ret = adb('push', data_dir, '/sdcard/viser/data', serial=serial) |
| if 0 < ret.returncode: |
| print('Could not push the scenarios data, error was: {}'.format(ret.stderr), |
| file=sys.stderr) |
| else: |
| print('Scenarios data pushed.') |
| |
| # Install the smartviser apps (starting with the proxy app) |
| for app in [PREBUILT_PROXY_APKS[flavour]] + PREBUILT_APKS: |
| install_apk(serial, app, prebuilts_dir) |
| |
| |
| # Grant the permissions through the UI |
| def configure_perms(dut): |
| dut() \ |
| .child_by_text('You need to grant access for the Permissions Group ' |
| 'Calendar Camera Contacts Microphone SMS Storage Telephone Location', |
| resourceId='android:id/content') \ |
| .child(text='OK', className='android.widget.Button') \ |
| .click() |
| |
| # Accept any permission that is asked for |
| prompt = dut(resourceId='com.android.packageinstaller:id/permission_allow_button', |
| className='android.widget.Button') |
| while prompt.exists: |
| prompt.click() |
| |
| # Input the credentials |
| dut(resourceId='android:id/content') \ |
| .child(text='Username') \ |
| .child(className='android.widget.EditText') \ |
| .set_text('borjan@fairphone.com') |
| dut(resourceId='android:id/content') \ |
| .child(text='Password') \ |
| .child(className='android.widget.EditText') \ |
| .set_text('Rickisalso1niceguy!') |
| |
| # Sign in |
| dut(resourceId='android:id/content') \ |
| .child(text='Sign in', className='android.widget.Button') \ |
| .click() |
| |
| def configure_sms(dut): |
| # TODO wait for the connection to be established and time-out |
| prompt = dut(resourceId='android:id/content') \ |
| .child(text='Viser must be your SMS app to send messages') |
| while not prompt.exists: |
| time.sleep(1) |
| |
| # Make viser the default SMS app |
| dut(resourceId='android:id/content') \ |
| .child_by_text('Viser must be your SMS app to send messages', |
| className='android.widget.LinearLayout') \ |
| .child(text='OK', className='android.widget.Button') \ |
| .click() |
| |
| dut(resourceId='android:id/content') \ |
| .child_by_text('Change SMS app?', className='android.widget.LinearLayout') \ |
| .child(text='Yes', className='android.widget.Button') \ |
| .click() |
| |
| def configure_settings(dut): |
| # Set the e-mail account |
| dut(text='Settings', className='android.widget.TextView') \ |
| .click() |
| dut(resourceId='android:id/list') \ |
| .child_by_text('User settings', className='android.widget.LinearLayout') \ |
| .click() |
| dut(resourceId='android:id/list') \ |
| .child_by_text('Email account', className='android.widget.LinearLayout') \ |
| .click() |
| prompt = dut(resourceId='android:id/content') \ |
| .child_by_text('Email account', className='android.widget.LinearLayout') |
| prompt.child(resourceId='android:id/edit') \ |
| .set_text('fairphone.viser@gmail.com') |
| prompt.child(text='OK', className='android.widget.Button') \ |
| .click() |
| dut(resourceId='android:id/list') \ |
| .child_by_text('Email password', className='android.widget.LinearLayout') \ |
| .click() |
| dut(text='Password :') \ |
| .child(className='android.widget.EditText') \ |
| .set_text('fairphoneviser2017') |
| dut(description='OK', className='android.widget.TextView') \ |
| .click() |
| dut.press.back() |
| dut.press.back() |
| |
| |
| def deploy(): |
| serials = [] |
| |
| if len(sys.argv) > 1: |
| serials.append(sys.argv[1]) |
| else: |
| # List devices attached to adb |
| ret = subprocess.run( |
| "adb devices | grep -E '^[a-z0-9-]+\s+device$' | awk '{{print $1}}'", |
| shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, |
| universal_newlines=True) |
| |
| if 0 < ret.returncode: |
| # TODO handle the error |
| raise Exception('Failed to list adb devices') |
| elif ret.stdout: |
| for line in ret.stdout.splitlines(): |
| serial = line.strip() |
| if serial: |
| serials.append(serial) |
| |
| for serial in serials: |
| print('Configuring device {}'.format(serial)) |
| |
| dut = Device(serial) |
| |
| # Make sure the screen stays on - we're going to use UI automation |
| force_awake(serial) |
| unlock(serial) |
| |
| # Push the scenarios, their data, and install the apps |
| prepare_dut(serial, '../scenarios', '../scenarios-data', PREBUILTS_PATH) |
| |
| # Start the viser app |
| ret = adb( |
| 'shell', 'monkey', '-p', 'com.lunarlabs.panda', '-c', |
| 'android.intent.category.LAUNCHER', '1', serial=serial) |
| if 0 < ret.returncode: |
| print('Could not start the viser app, error was: {}'.format(app, |
| ret.stderr), file=sys.stderr) |
| exit(-1) |
| |
| configure_perms(dut) |
| |
| # TODO DO NOT DO THE FOLLOWING IF NO SIM CARD IS IN THE DUT |
| # time.sleep(10) |
| # configure_sms(dut) |
| |
| configure_settings(dut) |
| |
| # Leave the device alone now |
| force_awake(serial, always=False) |
| |
| |
| if __name__ == '__main__': |
| deploy() |