| #!/bin/sh |
| |
| # Copyright 2018 Fairphone B.V. |
| # |
| # 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. |
| # |
| |
| set -o errexit |
| set -o nounset |
| |
| # Print the extended trace if we are verbose. Do not disable the trace if we are |
| # not verbose to keep what the environment defines. |
| if [ "${VERBOSE:-false}" = true ]; then |
| set -o xtrace |
| fi |
| |
| usage() { |
| cat <<heredoc |
| Usage: $0 [-h] [-s SERIAL] OTA_FILE |
| Copy an OTA update file to a device |
| |
| Options: |
| -h, --help Print this help message. |
| -r, --click_reboot Automatically hit the reboot button for starting the |
| installation of the OTA package. This needs a Python 3 |
| environment with uiautomator installed. |
| Run \`pip3 install uiautomator\` to install it in your |
| (virtual) environment. |
| -s, --serial Specify a target device based on its adb serial number. |
| Globals: |
| VERBOSE Set to "true" to enable xtrace (shell debug output). |
| Otherwise, the xtrace option will be left as it is. |
| heredoc |
| } |
| |
| SERIAL= |
| ADB_ARGUMENTS= |
| LOG_PREFIX= |
| TARGET_DIR=/mnt/sdcard/Download |
| CLICK_REBOOT=false |
| UPDATER_ACTIVITY= |
| |
| log() { |
| echo "${LOG_PREFIX}$*" |
| } |
| |
| kill_updater() { |
| adb ${ADB_ARGUMENTS} shell am force-stop com.fairphone.updater |
| } |
| |
| click_reboot() { |
| # shellcheck disable=SC2039 |
| local serial_string="" |
| if [ -n "${SERIAL}" ]; then |
| serial_string="\"${SERIAL}\"" |
| fi |
| python3 -c \ |
| "from uiautomator import Device |
| dut = Device(${serial_string}) |
| button = dut(resourceId=\"com.fairphone.updater:id/restart_button\") |
| button.click()" |
| } |
| |
| trigger_update() { |
| # Make sure we start off with a fresh state, especially in cases of app crashes. |
| log "Killing the Updater if it is currently running…" |
| kill_updater || true |
| |
| log "Starting the Updater activity…" |
| adb ${ADB_ARGUMENTS} shell am start -a android.intent.action.VIEW \ |
| -t application/zip \ |
| -c android.intent.category.DEFAULT \ |
| -d "file:///${TARGET_DIR}/$(basename "${OTA_UPDATE_FILE}")" \ |
| "${UPDATER_ACTIVITY}" |
| |
| if [ "${CLICK_REBOOT}" = true ]; then |
| log "Triggering reboot for installing the OTA…" |
| # Trigger UI automation. This may fail due to a bug in uiautomator: |
| # https://github.com/xiaocong/uiautomator/issues/210 |
| # Retrying a couple of times usually fixes the issue here. |
| # shellcheck disable=SC2039 |
| local ui_max_tries=5 |
| # shellcheck disable=SC2039 |
| local ui_retry_wait_time=20s |
| # shellcheck disable=SC2039 |
| local i |
| for i in $(seq "${ui_max_tries}"); do |
| adb ${ADB_ARGUMENTS} wait-for-device |
| if click_reboot; then |
| break; |
| fi |
| if [ "${i}" -eq "${ui_max_tries}" ]; then |
| log "click_reboot failed too often. Aborting." |
| return 1 |
| fi |
| log "click_reboot: Try $i failed. \ |
| Trying again in ${ui_retry_wait_time}." |
| sleep "${ui_retry_wait_time}" |
| done |
| fi |
| } |
| |
| # Allow to automatically retry occasionally failing commands. |
| # This is a simplistic workaround for various kinds of failures. The number of |
| # retries and wait time in between is therefore fairly arbitrary and result of |
| # experimentation. |
| retry_cmd() { |
| # shellcheck disable=SC2039 |
| local max_tries=5 |
| # shellcheck disable=SC2039 |
| local i |
| for i in $(seq "${max_tries}"); do |
| # shellcheck disable=SC2039 |
| local ret_val=0 |
| "$@" || ret_val=$? |
| if [ "${ret_val}" -eq 0 ]; then |
| return 0 |
| fi |
| log "Following command failed: $*" |
| if [ "$i" -eq "${max_tries}" ]; then |
| log "Too many failures (${max_tries}), aborting." |
| return "${ret_val}" |
| fi |
| log "Try $i of ${max_tries} failed. Trying again." |
| sleep 10s |
| done |
| |
| return "${ret_val}" |
| } |
| |
| while [ $# -gt 1 ]; do |
| case "$1" in |
| -r|--click_reboot) |
| CLICK_REBOOT=true |
| shift |
| ;; |
| -s|--serial) |
| if [ $# -lt 2 ]; then |
| echo "Missing device serial number" >&2 |
| usage >&2 |
| exit 1 |
| fi |
| SERIAL="$2" |
| ADB_ARGUMENTS="${ADB_ARGUMENTS} -s ${SERIAL}" |
| LOG_PREFIX="[$2] " |
| shift 2 |
| ;; |
| -h|--help) |
| usage |
| exit 0 |
| ;; |
| *) |
| echo "Unknown option $*" >&2 |
| usage >&2 |
| exit 1 |
| ;; |
| esac |
| done |
| |
| if [ $# -ne 1 ]; then |
| echo "Missing OTA update file." >&2 |
| usage >&2 |
| exit 1 |
| fi |
| |
| OTA_UPDATE_FILE="$1" |
| |
| if [ ! -f "${OTA_UPDATE_FILE}" ]; then |
| echo "${OTA_UPDATE_FILE} not found" >&2 |
| exit 1 |
| fi |
| |
| log "Waiting for device to be available…" |
| |
| adb ${ADB_ARGUMENTS} wait-for-device |
| |
| ANDROID_API_LEVEL="$(adb ${ADB_ARGUMENTS} shell getprop ro.build.version.sdk)" |
| |
| log "Pushing ${OTA_UPDATE_FILE} to device…" |
| |
| # `adb push` occasionally fails for unclear reasons. Usually this can be fixed |
| # by running the same command again. |
| retry_cmd adb ${ADB_ARGUMENTS} push "${OTA_UPDATE_FILE}" "${TARGET_DIR}/" |
| |
| if [ "${ANDROID_API_LEVEL}" -ge 23 ]; then |
| # Android 6 introduces post-install/run-time permissions. |
| log "Granting storage read permission to the Updater…" |
| adb ${ADB_ARGUMENTS} shell pm grant com.fairphone.updater \ |
| android.permission.READ_EXTERNAL_STORAGE |
| fi |
| |
| # The activity was renamed from FairphoneUpdater to UpdaterActivity in v1.39.9 |
| UPDATER_ACTIVITY=$(adb ${ADB_ARGUMENTS} shell dumpsys package \ |
| com.fairphone.updater \ |
| | dos2unix \ |
| | grep -A 2 "Base MIME Types:" \ |
| | sed -n -e 's/^.*\(com.fairphone.updater\/.*\)$/\1/p' \ |
| | awk '{print $1;}') |
| |
| # Trigger the Updater activity and also reboot, if requested. |
| # In specific cases and Updater versions, the Updater may crash here. Try working around that by |
| # trying again after a fresh start of the app. |
| retry_cmd trigger_update |