tMerge pull request #4864 from SomberNight/android_build_2018nov - electrum - Electrum Bitcoin wallet
 (HTM) git clone https://git.parazyd.org/electrum
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Submodules
       ---
 (DIR) commit 12c6a4043b4e5054eedf819657a28e30377a6d47
 (DIR) parent a53dded50fad595dce34be609c59fddad61d66be
 (HTM) Author: ghost43 <somber.night@protonmail.com>
       Date:   Mon, 26 Nov 2018 22:02:52 +0100
       
       Merge pull request #4864 from SomberNight/android_build_2018nov
       
       android: build apk using new python3 p4a toolchain
       Diffstat:
         M electrum/gui/kivy/Readme.md         |      81 +++++++++++++++++++------------
         M electrum/gui/kivy/data/java-classe… |      65 +++++++++++++++++++++++++------
         M electrum/gui/kivy/tools/buildozer.… |      14 +++++++-------
         M electrum/util.py                    |      34 ++-----------------------------
       
       4 files changed, 112 insertions(+), 82 deletions(-)
       ---
 (DIR) diff --git a/electrum/gui/kivy/Readme.md b/electrum/gui/kivy/Readme.md
       t@@ -1,6 +1,9 @@
        # Kivy GUI
        
       -The Kivy GUI is used with Electrum on Android devices. To generate an APK file, follow these instructions.
       +The Kivy GUI is used with Electrum on Android devices.
       +To generate an APK file, follow these instructions.
       +
       +Recommended env: Ubuntu 18.04
        
        ## 1. Preliminaries
        
       t@@ -21,10 +24,7 @@ sudo apt-get install python3-kivy
        
        ## 3. Install python-for-android (p4a)
        p4a is used to package Electrum, Python, SDL and a bootstrap Java app into an APK file. 
       -We patched p4a to add some functionality we need for Electrum. Until those changes are
       -merged into p4a, you need to merge them locally (into the master branch):
       -
       -3.1 [kivy/python-for-android#1217](https://github.com/kivy/python-for-android/pull/1217)
       +We need some functionality not in p4a master, so for the time being we have our own fork.
        
        Something like this should work:
        
       t@@ -32,12 +32,9 @@ Something like this should work:
        cd /opt
        git clone https://github.com/kivy/python-for-android
        cd python-for-android
       -git remote add agilewalker https://github.com/agilewalker/python-for-android
        git remote add sombernight https://github.com/SomberNight/python-for-android
        git fetch --all
       -git checkout 93759f36ba45c7bbe0456a4b3e6788622924cbac
       -git cherry-pick a2fb5ecbc09c4847adbcfd03c6b1ca62b3d09b8d  # openssl-fix
       -git cherry-pick a0ef2007bc60ed642fbd8b61937995dbed0ddd24  # disable backups
       +git checkout f74226666af69f9915afaee9ef9292db85a6c617
        ```
        
        ## 4. Install buildozer
       t@@ -51,31 +48,57 @@ sudo python3 setup.py install
        ```
        
        4.2 Install additional dependencies:
       +
        ```sh
        sudo apt-get install python-pip
        ```
       -and the ones listed
       -[here](https://buildozer.readthedocs.io/en/latest/installation.html#targeting-android).
        
       -You will also need
       +(from [buildozer docs](https://buildozer.readthedocs.io/en/latest/installation.html#targeting-android))
       +```sh
       +sudo pip install --upgrade cython==0.21
       +sudo dpkg --add-architecture i386
       +sudo apt-get update
       +sudo apt-get install build-essential ccache git libncurses5:i386 libstdc++6:i386 libgtk2.0-0:i386 libpangox-1.0-0:i386 libpangoxft-1.0-0:i386 libidn11:i386 python2.7 python2.7-dev openjdk-8-jdk unzip zlib1g-dev zlib1g:i386
       +```
       +
       +4.3 Download Android NDK
        ```sh
       -python3 -m pip install colorama appdirs sh jinja2
       +cd /opt
       +wget https://dl.google.com/android/repository/android-ndk-r14b-linux-x86_64.zip
       +unzip android-ndk-r14b-linux-x86_64.zip
        ```
        
       +## 5. Some more dependencies
        
       -4.3 Download the [Crystax NDK](https://www.crystax.net/en/download) manually.
       -Extract into `/opt/crystax-ndk-10.3.2`
       +```sh
       +python3 -m pip install colorama appdirs sh jinja2 cython==0.29
       +sudo apt-get install autotools-dev autoconf libtool pkg-config python3.7
       +```
        
        
       -## 5. Create the UI Atlas
       -In the `gui/kivy` directory of Electrum, run `make theming`.
       +## 6. Create the UI Atlas
       +In the `electrum/gui/kivy` directory of Electrum, run `make theming`.
        
       -## 6. Download Electrum dependencies
       +## 7. Download Electrum dependencies
        ```sh
        sudo contrib/make_packages
        ```
        
       -## 7. Try building the APK and fail
       +## 8. Try building the APK and fail
       +
       +### 1. Try and fail:
       +
       +```sh
       +contrib/make_apk
       +```
       +
       +Symlink android tools:
       +
       +```sh
       +ln -sf ~/.buildozer/android/platform/android-sdk-24/tools ~/.buildozer/android/platform/android-sdk-24/tools.save
       +```
       +
       +### 2. Try and fail:
        
        ```sh
        contrib/make_apk
       t@@ -84,47 +107,43 @@ contrib/make_apk
        During this build attempt, buildozer downloaded some tools,
        e.g. those needed in the next step.
        
       -## 8. Update the Android SDK build tools
       +## 9. Update the Android SDK build tools
        
        ### Method 1: Using the GUI
        
          Start the Android SDK manager in GUI mode:
          
       -    ~/.buildozer/android/platform/android-sdk-20/tools/android
       +    ~/.buildozer/android/platform/android-sdk-24/tools/android
        
          Check the latest SDK available and install it
          ("Android SDK Tools" and "Android SDK Platform-tools").
          Close the SDK manager. Repeat until there is no newer version.
          
          Reopen the SDK manager, and install the latest build tools
       -  ("Android SDK Build-tools"), 27.0.3 at the time of writing.
       +  ("Android SDK Build-tools"), 28.0.3 at the time of writing.
          
       +  Install "Android 9">"SDK Platform".
          Install "Android Support Repository" from the SDK manager (under "Extras").
        
        ### Method 2: Using the command line:
        
          Repeat the following command until there is nothing to install:
        
       -    ~/.buildozer/android/platform/android-sdk-20/tools/android update sdk -u -t tools,platform-tools
       +    ~/.buildozer/android/platform/android-sdk-24/tools/android update sdk -u -t tools,platform-tools
        
          Install Build Tools, android API 19 and Android Support Library:
        
       -    ~/.buildozer/android/platform/android-sdk-20/tools/android update sdk -u -t build-tools-27.0.3,android-19,extra-android-m2repository
       +    ~/.buildozer/android/platform/android-sdk-24/tools/android update sdk -u -t build-tools-28.0.3,android-28,extra-android-m2repository
        
       +  (FIXME: build-tools is not getting installed?! use GUI for now.)
        
       -## 9. Build the APK
       +## 10. Build the APK
        
        ```sh
        contrib/make_apk
        ```
        
        # FAQ
       -## Why do I get errors like `package me.dm7.barcodescanner.zxing does not exist` while compiling?
       -Update your Android build tools to version 27 like described above.
       -
       -## Why do I get errors like  `(use -source 7 or higher to enable multi-catch statement)` while compiling?
       -Make sure that your p4a installation includes commit a3cc78a6d1a107cd3b6bd28db8b80f89e3ecddd2.
       -Also make sure you have recent SDK tools and platform-tools
        
        ## I changed something but I don't see any differences on the phone. What did I do wrong?
        You probably need to clear the cache: `rm -rf .buildozer/android/platform/build/{build,dists}`
 (DIR) diff --git a/electrum/gui/kivy/data/java-classes/org/electrum/qr/SimpleScannerActivity.java b/electrum/gui/kivy/data/java-classes/org/electrum/qr/SimpleScannerActivity.java
       t@@ -4,6 +4,9 @@ import android.app.Activity;
        import android.os.Bundle;
        import android.util.Log;
        import android.content.Intent;
       +import android.support.v4.app.ActivityCompat;
       +import android.Manifest;
       +import android.content.pm.PackageManager;
        
        import java.util.Arrays;
        
       t@@ -13,28 +16,35 @@ import com.google.zxing.Result;
        import com.google.zxing.BarcodeFormat;
        
        public class SimpleScannerActivity extends Activity implements ZXingScannerView.ResultHandler {
       -    private ZXingScannerView mScannerView;
       -    final String TAG = "org.electrum.SimpleScannerActivity";
       +    private static final int MY_PERMISSIONS_CAMERA = 1002;
        
       -    @Override
       -    public void onCreate(Bundle state) {
       -        super.onCreate(state);
       -        mScannerView = new ZXingScannerView(this);   // Programmatically initialize the scanner view
       -        mScannerView.setFormats(Arrays.asList(BarcodeFormat.QR_CODE));
       -        setContentView(mScannerView);                // Set the scanner view as the content view
       -    }
       +    private ZXingScannerView mScannerView = null;
       +    final String TAG = "org.electrum.SimpleScannerActivity";
        
            @Override
            public void onResume() {
                super.onResume();
       -        mScannerView.setResultHandler(this); // Register ourselves as a handler for scan results.
       -        mScannerView.startCamera();          // Start camera on resume
       +        if (this.hasPermission()) {
       +            this.startCamera();
       +        } else {
       +            this.requestPermission();
       +        }
            }
        
            @Override
            public void onPause() {
                super.onPause();
       -        mScannerView.stopCamera();           // Stop camera on pause
       +        if (null != mScannerView) {
       +            mScannerView.stopCamera();           // Stop camera on pause
       +        }
       +    }
       +
       +    private void startCamera() {
       +        mScannerView = new ZXingScannerView(this);   // Programmatically initialize the scanner view
       +        mScannerView.setFormats(Arrays.asList(BarcodeFormat.QR_CODE));
       +        setContentView(mScannerView);                // Set the scanner view as the content view
       +        mScannerView.setResultHandler(this);         // Register ourselves as a handler for scan results.
       +        mScannerView.startCamera();                  // Start camera on resume
            }
        
            @Override
       t@@ -45,4 +55,35 @@ public class SimpleScannerActivity extends Activity implements ZXingScannerView.
                setResult(Activity.RESULT_OK, resultIntent);
                this.finish();
            }
       +
       +    private boolean hasPermission() {
       +        return (ActivityCompat.checkSelfPermission(this,
       +                                                   Manifest.permission.CAMERA)
       +                == PackageManager.PERMISSION_GRANTED);
       +    }
       +
       +    private void requestPermission() {
       +        ActivityCompat.requestPermissions(this,
       +                    new String[]{Manifest.permission.CAMERA},
       +                    MY_PERMISSIONS_CAMERA);
       +    }
       +
       +    @Override
       +    public void onRequestPermissionsResult(int requestCode,
       +            String permissions[], int[] grantResults) {
       +        switch (requestCode) {
       +            case MY_PERMISSIONS_CAMERA: {
       +                if (grantResults.length > 0
       +                    && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
       +                    // permission was granted, yay!
       +                    this.startCamera();
       +                } else {
       +                    // permission denied
       +                    this.finish();
       +                }
       +                return;
       +            }
       +        }
       +    }
       +
        }
 (DIR) diff --git a/electrum/gui/kivy/tools/buildozer.spec b/electrum/gui/kivy/tools/buildozer.spec
       t@@ -31,7 +31,7 @@ version.filename = %(source.dir)s/electrum/version.py
        #version = 1.9.8
        
        # (list) Application requirements
       -requirements = python3crystax==3.6, android, openssl, plyer, kivy==master, libsecp256k1
       +requirements = python3, android, openssl, plyer, kivy==master, libffi, libsecp256k1
        
        # (str) Presplash of the application
        #presplash.filename = %(source.dir)s/gui/kivy/theming/splash.png
       t@@ -52,25 +52,25 @@ fullscreen = False
        #
        
        # (list) Permissions
       -android.permissions = INTERNET, WRITE_EXTERNAL_STORAGE, READ_EXTERNAL_STORAGE, CAMERA
       +android.permissions = INTERNET, CAMERA
        
        # (int) Android API to use
       -#android.api = 14
       +android.api = 28
        
        # (int) Minimum API required (8 = Android 2.2 devices)
       -#android.minapi = 8
       +android.minapi = 21
        
        # (int) Android SDK version to use
       -#android.sdk = 21
       +android.sdk = 24
        
        # (str) Android NDK version to use
       -#android.ndk = 9
       +android.ndk = 14b
        
        # (bool) Use --private data storage (True) or --dir public storage (False)
        android.private_storage = True
        
        # (str) Android NDK directory (if empty, it will be automatically downloaded.)
       -android.ndk_path = /opt/crystax-ndk-10.3.2
       +android.ndk_path = /opt/android-ndk-r14b
        
        # (str) Android SDK directory (if empty, it will be automatically downloaded.)
        #android.sdk_path =
 (DIR) diff --git a/electrum/util.py b/electrum/util.py
       t@@ -354,41 +354,11 @@ def profiler(func):
            return lambda *args, **kw_args: do_profile(args, kw_args)
        
        
       -def android_ext_dir():
       -    import jnius
       -    env = jnius.autoclass('android.os.Environment')
       -    return env.getExternalStorageDirectory().getPath()
       -
        def android_data_dir():
            import jnius
            PythonActivity = jnius.autoclass('org.kivy.android.PythonActivity')
            return PythonActivity.mActivity.getFilesDir().getPath() + '/data'
        
       -def android_headers_dir():
       -    d = android_ext_dir() + '/org.electrum.electrum'
       -    if not os.path.exists(d):
       -        try:
       -            os.mkdir(d)
       -        except FileExistsError:
       -            pass  # in case of race
       -    return d
       -
       -def android_check_data_dir():
       -    """ if needed, move old directory to sandbox """
       -    ext_dir = android_ext_dir()
       -    data_dir = android_data_dir()
       -    old_electrum_dir = ext_dir + '/electrum'
       -    if not os.path.exists(data_dir) and os.path.exists(old_electrum_dir):
       -        import shutil
       -        new_headers_path = android_headers_dir() + '/blockchain_headers'
       -        old_headers_path = old_electrum_dir + '/blockchain_headers'
       -        if not os.path.exists(new_headers_path) and os.path.exists(old_headers_path):
       -            print_error("Moving headers file to", new_headers_path)
       -            shutil.move(old_headers_path, new_headers_path)
       -        print_error("Moving data to", data_dir)
       -        shutil.move(old_electrum_dir, data_dir)
       -    return data_dir
       -
        
        def ensure_sparse_file(filename):
            # On modern Linux, no need to do anything.
       t@@ -401,7 +371,7 @@ def ensure_sparse_file(filename):
        
        
        def get_headers_dir(config):
       -    return android_headers_dir() if 'ANDROID_DATA' in os.environ else config.path
       +    return config.path
        
        
        def assert_datadir_available(config_path):
       t@@ -484,7 +454,7 @@ def bh2u(x: bytes) -> str:
        
        def user_dir():
            if 'ANDROID_DATA' in os.environ:
       -        return android_check_data_dir()
       +        return android_data_dir()
            elif os.name == 'posix':
                return os.path.join(os.environ["HOME"], ".electrum")
            elif "APPDATA" in os.environ: