Skip to content

Commit 3c4c124

Browse files
Fix missing permissions for newer android version
1 parent 0e048e3 commit 3c4c124

File tree

6 files changed

+79
-44
lines changed

6 files changed

+79
-44
lines changed

.idea/misc.xml

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/build.gradle

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ android {
1010
minSdkVersion 16
1111
targetSdkVersion 35
1212
versionCode 20
13-
versionName "0.0.10"
13+
versionName "0.0.11"
1414
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
1515
}
1616

@@ -85,4 +85,8 @@ dependencies {
8585

8686
// required to avoid crash on Android 12 API 31
8787
implementation 'androidx.work:work-runtime-ktx:2.7.1'
88+
89+
// Token bucket
90+
implementation ('org.isomorphism:token-bucket:1.7')
91+
implementation 'com.google.guava:guava:33.3.1-android'
8892
}

app/src/main/java/com/albertogeniola/merossconf/ui/fragments/pair/AbstractWifiFragment.java

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,7 @@ public abstract class AbstractWifiFragment extends Fragment {
4444
private final Timer mTimer;
4545
private WifiManager mWifiManager;
4646
private ConnectivityManager mConnectivityManager;
47-
private static final String[] WIFI_PERMS = new String[] {
48-
Manifest.permission.NEARBY_WIFI_DEVICES,
49-
Manifest.permission.ACCESS_FINE_LOCATION,
50-
Manifest.permission.CHANGE_WIFI_STATE,
51-
Manifest.permission.CHANGE_NETWORK_STATE};
52-
47+
private static String[] WIFI_PERMS = null;
5348
private static final int REQUEST_PERMISSION_CODE = 1;
5449

5550
// holds the state of the wifi connection attempt
@@ -85,10 +80,24 @@ public AbstractWifiFragment() {
8580
public void onCreate(Bundle savedInstanceState) {
8681
super.onCreate(savedInstanceState);
8782

83+
if (android.os.Build.VERSION.SDK_INT <= 32) {
84+
WIFI_PERMS = new String[]{
85+
Manifest.permission.ACCESS_COARSE_LOCATION,
86+
Manifest.permission.ACCESS_FINE_LOCATION,
87+
Manifest.permission.CHANGE_WIFI_STATE,
88+
Manifest.permission.CHANGE_NETWORK_STATE};
89+
} else {
90+
WIFI_PERMS = new String[]{
91+
Manifest.permission.NEARBY_WIFI_DEVICES,
92+
Manifest.permission.ACCESS_FINE_LOCATION,
93+
Manifest.permission.CHANGE_WIFI_STATE,
94+
Manifest.permission.CHANGE_NETWORK_STATE};
95+
}
96+
8897
multiplePermissionsContract = new ActivityResultContracts.RequestMultiplePermissions();
8998
multiplePermissionLauncher = registerForActivityResult(multiplePermissionsContract, isGranted -> {
9099
if (isGranted.containsValue(false)) {
91-
multiplePermissionLauncher.launch(WIFI_PERMS);
100+
askPermissions(multiplePermissionLauncher);
92101
onMissingWifiPermissions(mTargetSsid);
93102
} else {
94103
onWifiPermissionsGranted(mTargetSsid, mTargetBssid);
@@ -116,7 +125,7 @@ public void onDestroyView() {
116125
super.onDestroyView();
117126

118127
// Unschedule the timeout task, if any
119-
if (mTimeoutTask!=null) {
128+
if (mTimeoutTask != null) {
120129
mTimeoutTask.cancel();
121130
mTimeoutTask = null;
122131
}
@@ -185,7 +194,6 @@ private boolean hasPermissions(String[] permissions) {
185194

186195
private void askPermissions(ActivityResultLauncher<String[]> multiplePermissionLauncher) {
187196
if (!hasPermissions(WIFI_PERMS)) {
188-
Log.d("PERMISSIONS", "Launching multiple contract permission launcher for ALL required permissions");
189197
multiplePermissionLauncher.launch(WIFI_PERMS);
190198
} else {
191199
Log.d("PERMISSIONS", "All permissions are already granted");
@@ -199,11 +207,12 @@ public void onResume() {
199207

200208
/**
201209
* Starts the wifi connection to the specified ssid
202-
* @param targetSsid Target Wifi SSID to connect to
203-
* @param targetBssid (Optional) Target BSSID
210+
*
211+
* @param targetSsid Target Wifi SSID to connect to
212+
* @param targetBssid (Optional) Target BSSID
204213
* @param targetPassphrase (Optional) Wifi passphrase to use
205-
* @param reason A reason to show to the user that explains why Wifi is being requested.
206-
* @param timeout Number of milliseconds to wait before issuing a timeout to the wifi operation
214+
* @param reason A reason to show to the user that explains why Wifi is being requested.
215+
* @param timeout Number of milliseconds to wait before issuing a timeout to the wifi operation
207216
* @throws PermissionNotGrantedException In case no enough permissions have been granted
208217
* to the app by the user. In such case, permissions
209218
* is automatically requested by this method.
@@ -223,7 +232,7 @@ public void startWifiConnection(String targetSsid,
223232
if (!AndroidUtils.checkPermissions(requireContext(), WIFI_PERMS)) {
224233

225234
// If a reason was specified, show it here.
226-
if (reason!=null) {
235+
if (reason != null) {
227236
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
228237
builder.setMessage(reason).setTitle("Wifi Access Request");
229238
AlertDialog dialog = builder.create();
@@ -232,7 +241,6 @@ public void startWifiConnection(String targetSsid,
232241

233242
// If the user did not yet grant Wifi and Location permissions, ask them here and abort the connection.
234243
askPermissions(multiplePermissionLauncher);
235-
//requestPermissions(WIFI_PERMS, REQUEST_PERMISSION_CODE);
236244

237245
// Return false, so the caller know it has to retry the operation after the
238246
// user's consent to use wifi
@@ -259,26 +267,28 @@ public void startWifiConnection(String targetSsid,
259267
* Starts the connection attempt against the legacy API.
260268
* To do so, it registers a broadcast receiver for the network connection events and
261269
* waits for the connection to attempt to the specified network.
270+
*
262271
* @param ssid
263272
* @param passphrase
264-
* @param timeout Timeout in milliseconds
273+
* @param timeout Timeout in milliseconds
265274
*/
266-
@SuppressLint("MissingPermission") //This piece of code is called only after permissions have bee granted
275+
@SuppressLint("MissingPermission")
276+
//This piece of code is called only after permissions have bee granted
267277
private void startWifiConnectionLegacy(String ssid, @Nullable String passphrase, int timeout) {
268278

269279
WifiConfiguration targetWifiConf = null;
270280

271281
// Locate the existing network configuration entry.
272282
for (android.net.wifi.WifiConfiguration conf : mWifiManager.getConfiguredNetworks()) {
273283

274-
if (conf.SSID.compareTo("\""+ssid+"\"") == 0) {
284+
if (conf.SSID.compareTo("\"" + ssid + "\"") == 0) {
275285
// Found a matching configuration: make sure the network passphrase is OK
276-
if (conf.preSharedKey != null && conf.preSharedKey.length()>0 && Strings.isEmpty(passphrase) || Strings.isEmpty(conf.preSharedKey) && !Strings.isEmpty(passphrase)) {
277-
Log.i(TAG, "Network configuration ("+conf.networkId+") ignored: passphrase mismatch.");
286+
if (conf.preSharedKey != null && conf.preSharedKey.length() > 0 && Strings.isEmpty(passphrase) || Strings.isEmpty(conf.preSharedKey) && !Strings.isEmpty(passphrase)) {
287+
Log.i(TAG, "Network configuration (" + conf.networkId + ") ignored: passphrase mismatch.");
278288
continue;
279289
}
280290
// If we reach this part, it means we found a valid, consistent wifi configuration to be used.
281-
Log.i(TAG, "Network configuration ("+conf.networkId+") found for ssid "+ssid+".");
291+
Log.i(TAG, "Network configuration (" + conf.networkId + ") found for ssid " + ssid + ".");
282292
targetWifiConf = conf;
283293
break;
284294
}
@@ -288,10 +298,10 @@ private void startWifiConnectionLegacy(String ssid, @Nullable String passphrase,
288298
if (targetWifiConf == null || targetWifiConf.networkId == -1) {
289299
Log.i(TAG, "Adding new Wifi configuration for the desired network.");
290300
targetWifiConf = new android.net.wifi.WifiConfiguration();
291-
targetWifiConf.SSID = "\""+ssid+"\"";
301+
targetWifiConf.SSID = "\"" + ssid + "\"";
292302

293303
if (!Strings.isEmpty(passphrase)) {
294-
targetWifiConf.preSharedKey = "\""+passphrase+"\"";
304+
targetWifiConf.preSharedKey = "\"" + passphrase + "\"";
295305
targetWifiConf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
296306
} else {
297307
targetWifiConf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
@@ -302,7 +312,7 @@ private void startWifiConnectionLegacy(String ssid, @Nullable String passphrase,
302312
}
303313

304314
// Disconnect from current network
305-
Log.i(TAG, "Forcing disconnection/reconnection to the selected wifi "+targetWifiConf);
315+
Log.i(TAG, "Forcing disconnection/reconnection to the selected wifi " + targetWifiConf);
306316
mWifiManager.disconnect();
307317

308318
// Start the timeout countdown
@@ -322,7 +332,7 @@ private void startWifiConnectionAndroidQ(String ssid, @Nullable String bssid, @N
322332
.setSsid(ssid);
323333

324334
if (bssid != null) {
325-
specifierBuilder.setBssid( MacAddress.fromString(bssid));
335+
specifierBuilder.setBssid(MacAddress.fromString(bssid));
326336
}
327337

328338
if (passphrase != null)
@@ -343,6 +353,7 @@ private void startWifiConnectionAndroidQ(String ssid, @Nullable String bssid, @N
343353
* Registers a broadcast received that listens to WIFI_STATE changes. It also sets the value
344354
* of the SSID to configure the broadcast receiver to intercept the desired network
345355
* connection.
356+
*
346357
* @param ssid SSID value that represent the network we are looking for
347358
*/
348359
private void registerWifiBroadcastReceiver(String ssid) {
@@ -392,7 +403,7 @@ private void unRegisterWifiBroadcastReceiver() {
392403

393404
private void notifyWifiConnected() {
394405
mWifiConnectionAttemptInProgress = false;
395-
if (mTimeoutTask!=null) {
406+
if (mTimeoutTask != null) {
396407
mTimeoutTask.cancel();
397408
mTimeoutTask = null;
398409
}
@@ -401,7 +412,7 @@ private void notifyWifiConnected() {
401412

402413
private void notifyWifiUnavailable() {
403414
mWifiConnectionAttemptInProgress = false;
404-
if (mTimeoutTask!=null) {
415+
if (mTimeoutTask != null) {
405416
mTimeoutTask.cancel();
406417
mTimeoutTask = null;
407418
}
@@ -452,7 +463,7 @@ public void run() {
452463
@Override
453464
public void onReceive(Context context, Intent intent) {
454465
String action = intent.getAction();
455-
if (WifiManager.NETWORK_STATE_CHANGED_ACTION .equals(action)) {
466+
if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
456467
NetworkInfo networkInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
457468
if (networkInfo == null) {
458469
Log.w(TAG, "Network info is null!");
@@ -463,7 +474,7 @@ public void onReceive(Context context, Intent intent) {
463474
String connectedSsid = null;
464475
String connectedBssid = null;
465476

466-
if (connectionInfo!=null) {
477+
if (connectionInfo != null) {
467478
connectedSsid = connectionInfo.getSSID();
468479
connectedBssid = connectionInfo.getBSSID();
469480
}
@@ -475,9 +486,9 @@ public void onReceive(Context context, Intent intent) {
475486
Log.i(TAG, "WifiState updated. Connected: " + networkInfo.isConnected() + ", SSID: " + connectedSsid);
476487

477488
if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI && networkInfo.isConnected()) {
478-
Log.i(TAG, "WifiState updated. Current SSID: "+connectedSsid);
489+
Log.i(TAG, "WifiState updated. Current SSID: " + connectedSsid);
479490

480-
String quotedSsid = "\""+mTargetSsid+"\"";
491+
String quotedSsid = "\"" + mTargetSsid + "\"";
481492
if (quotedSsid.compareTo(connectedSsid) == 0) {
482493
// Ok, we found the network we were looking for.
483494
notifyWifiConnected();

app/src/main/java/com/albertogeniola/merossconf/ui/fragments/pair/ScanDevicesFragment.java

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
import static android.Manifest.permission.CHANGE_WIFI_STATE;
77
import static android.Manifest.permission.NEARBY_WIFI_DEVICES;
88

9+
import android.annotation.SuppressLint;
910
import android.content.BroadcastReceiver;
1011
import android.content.Context;
1112
import android.content.DialogInterface;
1213
import android.content.Intent;
1314
import android.content.IntentFilter;
1415
import android.content.pm.PackageManager;
15-
import android.location.LocationManager;
1616
import android.net.wifi.ScanResult;
1717
import android.net.wifi.WifiManager;
1818
import android.os.Bundle;
@@ -51,33 +51,39 @@
5151
import com.google.android.material.floatingactionbutton.FloatingActionButton;
5252
import com.google.android.material.snackbar.Snackbar;
5353

54+
import org.isomorphism.util.TokenBucket;
55+
import org.isomorphism.util.TokenBuckets;
56+
5457
import java.util.ArrayList;
5558
import java.util.List;
59+
import java.util.concurrent.TimeUnit;
5660

5761
public class ScanDevicesFragment extends Fragment {
58-
private static final int REQUEST_PERMISSION_CODE = 1;
59-
private static final String TAG = "ScanDevicesFragment";
60-
6162
private PairActivityViewModel pairActivityViewModel;
6263

6364
private WifiManager wifiManager = null;
64-
private LocationManager locationManager = null;
6565
private ProgressBar scanningProgressBar;
6666
private FloatingActionButton fab;
6767
private MerossWifiScanAdapter adapter = new MerossWifiScanAdapter();
6868
private SwipeRefreshLayout swipeContainer;
6969
private Handler uiHandler;
7070
private boolean scanning;
7171
private ActivityResultLauncher<String[]> multiplePermissionLauncher;
72-
7372
private static String[] REQUIRED_PERMISSIONS = null;
74-
73+
private static final String STATE_N_TOKENS = "_state_num_tokens";
74+
private static final long DEFAULT_N_SCANS_PER_INTERVAL = 4;
75+
private static final long DEFAULT_INTERVAL_MINUTES = 2;
76+
private final TokenBucket scanBucket = TokenBuckets.builder().withCapacity(DEFAULT_N_SCANS_PER_INTERVAL).withFixedIntervalRefillStrategy(DEFAULT_N_SCANS_PER_INTERVAL, DEFAULT_INTERVAL_MINUTES, TimeUnit.MINUTES).build();
7577

7678
@Override
7779
public void onCreate(@Nullable Bundle savedInstanceState) {
7880
super.onCreate(savedInstanceState);
79-
81+
long tokens = DEFAULT_N_SCANS_PER_INTERVAL; // by default we are only allowed
82+
if (savedInstanceState != null)
83+
tokens = savedInstanceState.getLong(STATE_N_TOKENS);
84+
scanBucket.refill(tokens);
8085
if (android.os.Build.VERSION.SDK_INT > 32){
86+
// For releases > 32, we just need
8187
REQUIRED_PERMISSIONS = new String[] {
8288
NEARBY_WIFI_DEVICES,
8389
ACCESS_FINE_LOCATION,
@@ -86,7 +92,8 @@ public void onCreate(@Nullable Bundle savedInstanceState) {
8692
CHANGE_NETWORK_STATE
8793
};
8894
} else{
89-
// do something for phones running an SDK before lollipop
95+
// For releases <= 32, we need to ask also for location permissions in order to scan
96+
// wifis
9097
REQUIRED_PERMISSIONS = new String[] {
9198
ACCESS_FINE_LOCATION,
9299
ACCESS_COARSE_LOCATION,
@@ -95,7 +102,6 @@ public void onCreate(@Nullable Bundle savedInstanceState) {
95102
}
96103

97104
this.wifiManager = (WifiManager) getContext().getApplicationContext().getSystemService(Context.WIFI_SERVICE);
98-
this.locationManager = (LocationManager)getContext().getSystemService(Context.LOCATION_SERVICE);
99105
this.uiHandler = new Handler(Looper.getMainLooper());
100106
scanning = false;
101107
pairActivityViewModel = new ViewModelProvider(requireActivity()).get(PairActivityViewModel.class);
@@ -141,6 +147,12 @@ public void onDestroy() {
141147
super.onDestroy();
142148
}
143149

150+
@Override
151+
public void onSaveInstanceState(final Bundle outState) {
152+
super.onSaveInstanceState(outState);
153+
outState.putLong(STATE_N_TOKENS, scanBucket.getNumTokens());
154+
}
155+
144156
@Override
145157
public View onCreateView(
146158
LayoutInflater inflater, ViewGroup container,
@@ -244,6 +256,12 @@ public void onStart() {
244256
}
245257

246258
private void startScan() {
259+
if (!scanBucket.tryConsume(1)) {
260+
long seconds = scanBucket.getDurationUntilNextRefill(TimeUnit.SECONDS);
261+
Toast.makeText(ScanDevicesFragment.this.getContext(), "Scan rate limited. Wait " + seconds + " seconds before scanning again.", Toast.LENGTH_SHORT).show();
262+
return ;
263+
}
264+
247265
if (scanning) {
248266
Toast.makeText(ScanDevicesFragment.this.getContext(), "Scan already in progress.", Toast.LENGTH_SHORT).show();
249267
return;
@@ -261,7 +279,6 @@ private void startScan() {
261279
@Override
262280
public void onClick(DialogInterface dialog, int which) {
263281
askPermissions(multiplePermissionLauncher);
264-
//requestPermissions(REQUIRED_PERMISSIONS, REQUEST_PERMISSION_CODE);
265282
}
266283
});
267284
permissionAlert.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@@ -307,10 +324,12 @@ private void updateScanData(List<ScanResult> wifiNetworks) {
307324
this.scanning = false;
308325
}
309326

327+
@SuppressLint("MissingPermission") // Scan is only triggered upon permissions checks
310328
private void scanSuccess() {
311329
updateScanData(wifiManager.getScanResults());
312330
}
313331

332+
@SuppressLint("MissingPermission") // Scan is only triggered upon permissions checks
314333
private void scanFailure() {
315334
// handle failure: new scan did NOT succeed
316335
// consider using old scan results: these are the OLD results!

app/src/main/res/layout/activity_pair.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
tools:context=".PairActivity">
88

99
<com.google.android.material.appbar.AppBarLayout
10+
android:fitsSystemWindows="true"
1011
android:layout_width="match_parent"
1112
android:layout_height="wrap_content">
1213

app/src/main/res/layout/fragment_scan.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
android:layout_height="wrap_content"
3333
android:layout_gravity="bottom|end"
3434
android:layout_margin="@dimen/fab_margin"
35+
android:contentDescription="Scans the wifi networks"
3536
android:src="@drawable/ic_refresh_black_24dp"
3637
app:layout_constraintBottom_toBottomOf="parent"
3738
app:layout_constraintEnd_toEndOf="parent" />

0 commit comments

Comments
 (0)