Target android sdk 29 (#1575)

This commit is contained in:
Simon Frei 2020-12-12 16:20:06 +01:00 committed by GitHub
parent b04cacd8b4
commit b03d2465dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 129 additions and 112 deletions

View File

@ -25,8 +25,8 @@ dependencies {
android { android {
// Changes to these values need to be reflected in `.travis.yml` // Changes to these values need to be reflected in `.travis.yml`
compileSdkVersion 28 compileSdkVersion 29
buildToolsVersion '28.0.3' buildToolsVersion '29.0.3'
buildTypes.debug.applicationIdSuffix ".debug" buildTypes.debug.applicationIdSuffix ".debug"
dataBinding.enabled = true dataBinding.enabled = true
@ -39,7 +39,7 @@ android {
defaultConfig { defaultConfig {
applicationId "com.nutomic.syncthingandroid" applicationId "com.nutomic.syncthingandroid"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 28 targetSdkVersion 29
versionCode 4253 versionCode 4253
versionName "1.11.1" versionName "1.11.1"
testApplicationId 'com.nutomic.syncthingandroid.test' testApplicationId 'com.nutomic.syncthingandroid.test'

View File

@ -21,6 +21,8 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<!-- ACCESS_FINE_LOCATION is required to get WiFi's SSID on 10 "Q" --> <!-- ACCESS_FINE_LOCATION is required to get WiFi's SSID on 10 "Q" -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- ACCESS_BACKGROUND_LOCATION is required to get WiFi's SSID on 10 "Q" -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<!-- CAMERA is required for the QR Code Scanner --> <!-- CAMERA is required for the QR Code Scanner -->
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
@ -34,6 +36,7 @@
android:description="@string/app_description" android:description="@string/app_description"
android:supportsRtl="true" android:supportsRtl="true"
android:installLocation="internalOnly" android:installLocation="internalOnly"
android:requestLegacyExternalStorage="true"
android:name=".SyncthingApp"> android:name=".SyncthingApp">
<activity <activity
android:name=".activities.FirstStartActivity" android:name=".activities.FirstStartActivity"

View File

@ -39,8 +39,7 @@ import javax.inject.Inject;
public class FirstStartActivity extends Activity { public class FirstStartActivity extends Activity {
private static String TAG = "FirstStartActivity"; private static String TAG = "FirstStartActivity";
private static final int REQUEST_COARSE_LOCATION = 141;
private static final int REQUEST_WRITE_STORAGE = 142;
private static final int SLIDE_POS_LOCATION_PERMISSION = 1; private static final int SLIDE_POS_LOCATION_PERMISSION = 1;
private ViewPager mViewPager; private ViewPager mViewPager;
@ -300,8 +299,8 @@ public class FirstStartActivity extends Activity {
*/ */
private void requestLocationPermission() { private void requestLocationPermission() {
ActivityCompat.requestPermissions(this, ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, Constants.getLocationPermissions(),
REQUEST_COARSE_LOCATION); Constants.PermissionRequestType.LOCATION.ordinal());
} }
private boolean haveStoragePermission() { private boolean haveStoragePermission() {
@ -313,23 +312,32 @@ public class FirstStartActivity extends Activity {
private void requestStoragePermission() { private void requestStoragePermission() {
ActivityCompat.requestPermissions(this, ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
REQUEST_WRITE_STORAGE); Constants.PermissionRequestType.STORAGE.ordinal());
} }
@Override @Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) { @NonNull int[] grantResults) {
switch (requestCode) { switch (Constants.PermissionRequestType.values()[requestCode]) {
case REQUEST_COARSE_LOCATION: case LOCATION:
if (grantResults.length == 0 || boolean granted = grantResults.length != 0;
grantResults[0] != PackageManager.PERMISSION_GRANTED) { if (!granted) {
Log.i(TAG, "User denied ACCESS_COARSE_LOCATION permission."); Log.i(TAG, "No location permission in request-result");
} else { break;
}
for (int i = 0; i < grantResults.length; i++) {
if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
Log.i(TAG, "User granted permission: " + permissions[i]);
} else {
granted = false;
Log.i(TAG, "User denied permission: " + permissions[i]);
}
}
if (granted) {
Toast.makeText(this, R.string.permission_granted, Toast.LENGTH_SHORT).show(); Toast.makeText(this, R.string.permission_granted, Toast.LENGTH_SHORT).show();
Log.i(TAG, "User granted ACCESS_COARSE_LOCATION permission.");
} }
break; break;
case REQUEST_WRITE_STORAGE: case STORAGE:
if (grantResults.length == 0 || if (grantResults.length == 0 ||
grantResults[0] != PackageManager.PERMISSION_GRANTED) { grantResults[0] != PackageManager.PERMISSION_GRANTED) {
Log.i(TAG, "User denied WRITE_EXTERNAL_STORAGE permission."); Log.i(TAG, "User denied WRITE_EXTERNAL_STORAGE permission.");

View File

@ -66,21 +66,23 @@ public class SettingsActivity extends SyncthingActivity {
@Override @Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
// On Android 8.1, ACCESS_COARSE_LOCATION is required, see issue #999 if (requestCode == Constants.PermissionRequestType.LOCATION.ordinal()) {
if (requestCode == Constants.PERM_REQ_ACCESS_COARSE_LOCATION) { boolean granted = grantResults.length > 0;
for (int i = 0; i < permissions.length; i++) { for (int i = 0; i < grantResults.length; i++) {
if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permissions[i])) { if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { granted = false;
this.startService(new Intent(this, SyncthingService.class) break;
.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();
}
} }
} }
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();
}
} }
} }

View File

@ -1,5 +1,6 @@
package com.nutomic.syncthingandroid.service; package com.nutomic.syncthingandroid.service;
import android.Manifest;
import android.content.Context; import android.content.Context;
import android.os.Build; import android.os.Build;
import android.os.Environment; import android.os.Environment;
@ -48,10 +49,33 @@ public class Constants {
public static final String FOLDER_TYPE_RECEIVE_ONLY = "receiveonly"; public static final String FOLDER_TYPE_RECEIVE_ONLY = "receiveonly";
/** /**
* On Android 8.1, ACCESS_COARSE_LOCATION is required to access WiFi SSID. * These are the request codes used when requesting the permissions.
* This is the request code used when requesting the permission.
*/ */
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}). * Interval in ms at which the GUI is updated (eg {@link com.nutomic.syncthingandroid.fragments.DrawerFragment}).

View File

@ -5,6 +5,7 @@ import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager; import android.net.wifi.WifiManager;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
@ -18,6 +19,7 @@ import com.nutomic.syncthingandroid.R;
import com.nutomic.syncthingandroid.service.Constants; import com.nutomic.syncthingandroid.service.Constants;
import java.util.Arrays; import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -59,99 +61,76 @@ public class WifiSsidPreference extends MultiSelectListPreference {
*/ */
@Override @Override
protected void showDialog(Bundle state) { protected void showDialog(Bundle state) {
WifiConfiguration[] networks = loadConfiguredNetworksSorted(); Context context = getContext();
if (networks != null) {
Set<String> 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();
}
// On Android 8.1, ACCESS_COARSE_LOCATION is required, see issue #999 Set<String> selected = getSharedPreferences().getStringSet(getKey(), new HashSet<>());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { // from JavaDoc: Note that you must not modify the set instance returned by this call.
if (ContextCompat.checkSelfPermission(getContext(), Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { // therefore required to make a defensive copy of the elements
if (getContext() instanceof Activity) { selected = new HashSet<>(selected);
Activity activity = (Activity) getContext(); List<String> all = new ArrayList<>(selected);
ActivityCompat.requestPermissions(activity, new String[] { Manifest.permission.ACCESS_COARSE_LOCATION }, Constants.PERM_REQ_ACCESS_COARSE_LOCATION);
this.getDialog().dismiss(); // wait for result boolean connected = false;
} else { WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
Toast.makeText(getContext(), R.string.sync_only_wifi_ssids_need_to_grant_location_permission, Toast.LENGTH_LONG).show(); 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 * Checks if the required location permissions to obtain WiFi SSID are granted.
* removed from the allowed set by MultiSelectListPreference.
*/ */
private void filterRemovedNetworks(Set<String> selected, CharSequence[] all) { private boolean hasLocationPermissions() {
HashSet<CharSequence> availableNetworks = new HashSet<>(Arrays.asList(all)); String[] perms = Constants.getLocationPermissions();
selected.retainAll(availableNetworks); 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 * @param ssids the list of ssids to strip quotes from
* 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
*/ */
private CharSequence[] extractSsid(WifiConfiguration[] configs, boolean stripQuotes) { private CharSequence[] stripQuotes(List<String> ssids) {
CharSequence[] result = new CharSequence[configs.length]; CharSequence[] result = new CharSequence[ssids.size()];
for (int i = 0; i < configs.length; i++) { for (int i = 0; i < ssids.size(); i++) {
// See #620: there may be null-SSIDs result[i] = ssids.get(i).replaceFirst("^\"", "").replaceFirst("\"$", "");
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;
} }
return result; 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<WifiConfiguration> 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;
}
} }

View File

@ -324,6 +324,7 @@ Please report any problems you encounter via Github.</string>
<string name="run_on_all_wifi_networks">Run on all Wi-Fi networks.</string> <string name="run_on_all_wifi_networks">Run on all Wi-Fi networks.</string>
<string name="sync_only_wifi_ssids_wifi_turn_on_wifi">Please turn on Wi-Fi to select networks.</string> <string name="sync_only_wifi_ssids_wifi_turn_on_wifi">Please turn on Wi-Fi to select networks.</string>
<string name="sync_only_wifi_ssids_connect_to_wifi">Please connect to a Wi-Fi to add it to the list.</string>
<string name="sync_only_wifi_ssids_need_to_grant_location_permission">You need to grant LOCATION permission to use this feature.</string> <string name="sync_only_wifi_ssids_need_to_grant_location_permission">You need to grant LOCATION permission to use this feature.</string>

View File

@ -1,6 +1,6 @@
FROM openjdk:8 FROM openjdk:8
ENV GO_VERSION 1.15.2 ENV GO_VERSION 1.15.5
ENV ANDROID_SDK_VERSION 3859397 ENV ANDROID_SDK_VERSION 3859397
WORKDIR /opt WORKDIR /opt
@ -23,7 +23,7 @@ ENV ANDROID_HOME /opt/android-sdk
RUN yes | ${ANDROID_HOME}/tools/bin/sdkmanager --licenses RUN yes | ${ANDROID_HOME}/tools/bin/sdkmanager --licenses
# Install other android packages, including NDK # 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 # Accept licenses of newly installed packages
RUN yes | ${ANDROID_HOME}/tools/bin/sdkmanager --licenses RUN yes | ${ANDROID_HOME}/tools/bin/sdkmanager --licenses