From b03d2465dc134718134b0efed5cfeb5037b643dc Mon Sep 17 00:00:00 2001 From: Simon Frei Date: Sat, 12 Dec 2020 16:20:06 +0100 Subject: [PATCH] Target android sdk 29 (#1575) --- app/build.gradle | 6 +- app/src/main/AndroidManifest.xml | 3 + .../activities/FirstStartActivity.java | 34 +++-- .../activities/SettingsActivity.java | 28 ++-- .../syncthingandroid/service/Constants.java | 30 +++- .../views/WifiSsidPreference.java | 135 ++++++++---------- app/src/main/res/values/strings.xml | 1 + docker/Dockerfile | 4 +- 8 files changed, 129 insertions(+), 112 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 47b5545f..093149cc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -25,8 +25,8 @@ dependencies { android { // Changes to these values need to be reflected in `.travis.yml` - compileSdkVersion 28 - buildToolsVersion '28.0.3' + compileSdkVersion 29 + buildToolsVersion '29.0.3' buildTypes.debug.applicationIdSuffix ".debug" dataBinding.enabled = true @@ -39,7 +39,7 @@ android { defaultConfig { applicationId "com.nutomic.syncthingandroid" minSdkVersion 16 - targetSdkVersion 28 + targetSdkVersion 29 versionCode 4253 versionName "1.11.1" testApplicationId 'com.nutomic.syncthingandroid.test' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 65a0d570..3ebe7140 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,6 +21,8 @@ + + @@ -34,6 +36,7 @@ android:description="@string/app_description" android:supportsRtl="true" android:installLocation="internalOnly" + android:requestLegacyExternalStorage="true" android:name=".SyncthingApp"> 0; + for (int i = 0; i < grantResults.length; i++) { + if (grantResults[i] != PackageManager.PERMISSION_GRANTED) { + granted = false; + break; } } + if (granted) { + this.startService(new Intent(this, SyncthingService.class) + .setAction(SyncthingService.ACTION_REFRESH_NETWORK_INFO)); + } else { + Util.getAlertDialogBuilder(this) + .setTitle(R.string.sync_only_wifi_ssids_location_permission_rejected_dialog_title) + .setMessage(R.string.sync_only_wifi_ssids_location_permission_rejected_dialog_content) + .setPositiveButton(android.R.string.ok, null).show(); + } } } diff --git a/app/src/main/java/com/nutomic/syncthingandroid/service/Constants.java b/app/src/main/java/com/nutomic/syncthingandroid/service/Constants.java index 27aa7b5c..66cef47e 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/service/Constants.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/service/Constants.java @@ -1,5 +1,6 @@ package com.nutomic.syncthingandroid.service; +import android.Manifest; import android.content.Context; import android.os.Build; import android.os.Environment; @@ -48,10 +49,33 @@ public class Constants { public static final String FOLDER_TYPE_RECEIVE_ONLY = "receiveonly"; /** - * On Android 8.1, ACCESS_COARSE_LOCATION is required to access WiFi SSID. - * This is the request code used when requesting the permission. + * These are the request codes used when requesting the permissions. */ - public static final int PERM_REQ_ACCESS_COARSE_LOCATION = 999; // for issue #999 + public enum PermissionRequestType { + LOCATION, STORAGE + } + + /** + * Returns the location permissions required to access wifi SSIDs depending + * on the respective Android version. + */ + public static String[] getLocationPermissions() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { // before android 9 + return new String[]{ + Manifest.permission.ACCESS_COARSE_LOCATION, + }; + } + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P) { // android 9 + return new String[]{ + Manifest.permission.ACCESS_FINE_LOCATION, + }; + } + return new String[]{ // after android 9 + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_BACKGROUND_LOCATION, + }; + } + /** * Interval in ms at which the GUI is updated (eg {@link com.nutomic.syncthingandroid.fragments.DrawerFragment}). diff --git a/app/src/main/java/com/nutomic/syncthingandroid/views/WifiSsidPreference.java b/app/src/main/java/com/nutomic/syncthingandroid/views/WifiSsidPreference.java index c4377f43..1cade676 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/views/WifiSsidPreference.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/views/WifiSsidPreference.java @@ -5,6 +5,7 @@ import android.app.Activity; import android.content.Context; import android.content.pm.PackageManager; import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.os.Build; import android.os.Bundle; @@ -18,6 +19,7 @@ import com.nutomic.syncthingandroid.R; import com.nutomic.syncthingandroid.service.Constants; import java.util.Arrays; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -59,99 +61,76 @@ public class WifiSsidPreference extends MultiSelectListPreference { */ @Override protected void showDialog(Bundle state) { - WifiConfiguration[] networks = loadConfiguredNetworksSorted(); - if (networks != null) { - Set selected = getSharedPreferences().getStringSet(getKey(), new HashSet<>()); - // from JavaDoc: Note that you must not modify the set instance returned by this call. - // therefore required to make a defensive copy of the elements - selected = new HashSet<>(selected); - CharSequence[] all = extractSsid(networks, false); - filterRemovedNetworks(selected, all); - setEntries(extractSsid(networks, true)); // display without surrounding quotes - setEntryValues(all); // the value of the entry is the SSID "as is" - setValues(selected); // the currently selected values (without meanwhile deleted networks) - super.showDialog(state); - } else { - Toast.makeText(getContext(), R.string.sync_only_wifi_ssids_wifi_turn_on_wifi, Toast.LENGTH_LONG).show(); - } + Context context = getContext(); - // On Android 8.1, ACCESS_COARSE_LOCATION is required, see issue #999 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { - if (ContextCompat.checkSelfPermission(getContext(), Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { - if (getContext() instanceof Activity) { - Activity activity = (Activity) getContext(); - ActivityCompat.requestPermissions(activity, new String[] { Manifest.permission.ACCESS_COARSE_LOCATION }, Constants.PERM_REQ_ACCESS_COARSE_LOCATION); - this.getDialog().dismiss(); // wait for result - } else { - Toast.makeText(getContext(), R.string.sync_only_wifi_ssids_need_to_grant_location_permission, Toast.LENGTH_LONG).show(); + Set selected = getSharedPreferences().getStringSet(getKey(), new HashSet<>()); + // from JavaDoc: Note that you must not modify the set instance returned by this call. + // therefore required to make a defensive copy of the elements + selected = new HashSet<>(selected); + List all = new ArrayList<>(selected); + + boolean connected = false; + WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE); + if (wifiManager != null) { + WifiInfo info = wifiManager.getConnectionInfo(); + if (info != null) { + String ssid = info.getSSID(); + // api lvl 30 will have WifiManager.UNKNOWN_SSID + if (ssid != null && ssid != "" && !ssid.contains("unknown ssid")) { + if (!selected.contains(ssid)) { + all.add(ssid); + } + connected = true; } } } + + boolean hasPerms = hasLocationPermissions(); + if (!connected) { + if (!hasPerms) { + Toast.makeText(context, R.string.sync_only_wifi_ssids_need_to_grant_location_permission, Toast.LENGTH_LONG).show(); + } else { + Toast.makeText(context, R.string.sync_only_wifi_ssids_connect_to_wifi, Toast.LENGTH_LONG).show(); + } + } + + if (all.size() > 0 ) { + setEntries(stripQuotes(all)); // display without surrounding quotes + setEntryValues(all.toArray(new CharSequence[all.size()])); // the value of the entry is the SSID "as is" + setValues(selected); // the currently selected values (without meanwhile deleted networks) + super.showDialog(state); + } + + if (!hasPerms && context instanceof Activity) { + Activity activity = (Activity) context; + ActivityCompat.requestPermissions(activity, Constants.getLocationPermissions(), Constants.PermissionRequestType.LOCATION.ordinal()); + } } /** - * Removes any network that is no longer saved on the device. Otherwise it will never be - * removed from the allowed set by MultiSelectListPreference. + * Checks if the required location permissions to obtain WiFi SSID are granted. */ - private void filterRemovedNetworks(Set selected, CharSequence[] all) { - HashSet availableNetworks = new HashSet<>(Arrays.asList(all)); - selected.retainAll(availableNetworks); + private boolean hasLocationPermissions() { + String[] perms = Constants.getLocationPermissions(); + for (int i = 0; i < perms.length; i++) { + if (ContextCompat.checkSelfPermission(getContext(), perms[i]) != PackageManager.PERMISSION_GRANTED) { + return false; + } + } + return true; } /** - * Converts WiFi configuration to it's string representation, using the SSID. + * Returns a copy of the given WiFi SSIDs with quotes stripped. * - * It can also remove surrounding quotes which indicate that the SSID is an UTF-8 - * string and not a Hex-String, if the strings are intended to be displayed to the - * user, who will not expect the quotes. - * - * @param configs the objects to convert - * @param stripQuotes if to remove surrounding quotes - * @return the formatted SSID of the wifi configurations + * @param ssids the list of ssids to strip quotes from */ - private CharSequence[] extractSsid(WifiConfiguration[] configs, boolean stripQuotes) { - CharSequence[] result = new CharSequence[configs.length]; - for (int i = 0; i < configs.length; i++) { - // See #620: there may be null-SSIDs - String ssid = configs[i].SSID != null ? configs[i].SSID : ""; - // WiFi SSIDs can either be UTF-8 (encapsulated in '"') or hex-strings - if (stripQuotes) - result[i] = ssid.replaceFirst("^\"", "").replaceFirst("\"$", ""); - else - result[i] = ssid; + private CharSequence[] stripQuotes(List ssids) { + CharSequence[] result = new CharSequence[ssids.size()]; + for (int i = 0; i < ssids.size(); i++) { + result[i] = ssids.get(i).replaceFirst("^\"", "").replaceFirst("\"$", ""); } return result; } - /** - * Load the configured WiFi networks, sort them by SSID. - * - * @return a sorted array of WifiConfiguration, or null, if data cannot be retrieved - */ - private WifiConfiguration[] loadConfiguredNetworksSorted() { - WifiManager wifiManager = (WifiManager) - getContext().getApplicationContext().getSystemService(Context.WIFI_SERVICE); - if (wifiManager != null) { - List configuredNetworks = null; - try { - configuredNetworks = wifiManager.getConfiguredNetworks(); - } catch (SecurityException e) { - // See changes in Android Q, https://developer.android.com/reference/android/net/wifi/WifiManager.html#getConfiguredNetworks() - } - // if WiFi is turned off, getConfiguredNetworks returns null on many devices - if (configuredNetworks != null) { - WifiConfiguration[] result = configuredNetworks.toArray(new WifiConfiguration[configuredNetworks.size()]); - Arrays.sort(result, (lhs, rhs) -> { - // See #620: There may be null-SSIDs - String l = lhs.SSID != null ? lhs.SSID : ""; - String r = rhs.SSID != null ? rhs.SSID : ""; - return l.compareToIgnoreCase(r); - }); - return result; - } - } - // WiFi is turned off or device doesn't have WiFi - return null; - } - } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d9350c33..f8d6d4d2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -324,6 +324,7 @@ Please report any problems you encounter via Github. Run on all Wi-Fi networks. Please turn on Wi-Fi to select networks. + Please connect to a Wi-Fi to add it to the list. You need to grant LOCATION permission to use this feature. diff --git a/docker/Dockerfile b/docker/Dockerfile index ef687331..2db70d6a 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,6 +1,6 @@ FROM openjdk:8 -ENV GO_VERSION 1.15.2 +ENV GO_VERSION 1.15.5 ENV ANDROID_SDK_VERSION 3859397 WORKDIR /opt @@ -23,7 +23,7 @@ ENV ANDROID_HOME /opt/android-sdk RUN yes | ${ANDROID_HOME}/tools/bin/sdkmanager --licenses # Install other android packages, including NDK -RUN ${ANDROID_HOME}/tools/bin/sdkmanager tools platform-tools "build-tools;27.0.2" "platforms;android-27" "extras;android;m2repository" ndk-bundle +RUN ${ANDROID_HOME}/tools/bin/sdkmanager tools platform-tools "build-tools;29.0.3" "platforms;android-29" "extras;android;m2repository" ndk-bundle # Accept licenses of newly installed packages RUN yes | ${ANDROID_HOME}/tools/bin/sdkmanager --licenses