io.appium.settings 3.3.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -1
- package/apks/settings_apk-debug.apk +0 -0
- package/app/build.gradle +5 -6
- package/app/src/main/AndroidManifest.xml +5 -0
- package/app/src/main/java/io/appium/settings/ForegroundService.java +2 -0
- package/app/src/main/java/io/appium/settings/LocationService.java +9 -60
- package/app/src/main/java/io/appium/settings/LocationTracker.java +137 -114
- package/app/src/main/java/io/appium/settings/NLService.java +3 -1
- package/app/src/main/java/io/appium/settings/Settings.java +18 -20
- package/app/src/main/java/io/appium/settings/handlers/BluetoothConnectionSettingHandler.java +2 -0
- package/app/src/main/java/io/appium/settings/helpers/NotificationHelpers.java +3 -2
- package/app/src/main/java/io/appium/settings/location/FusedLocationProvider.java +9 -9
- package/app/src/main/java/io/appium/settings/location/LocationBuilder.java +80 -0
- package/app/src/main/java/io/appium/settings/receivers/LocationInfoReceiver.java +9 -2
- package/app/src/main/java/io/appium/settings/receivers/MediaScannerReceiver.java +91 -0
- package/app/src/main/java/io/appium/settings/receivers/NotificationsReceiver.java +3 -1
- package/app/src/main/java/io/appium/settings/receivers/UnpairBluetoothDevicesReceiver.java +2 -0
- package/build.gradle +1 -1
- package/gradle/wrapper/gradle-wrapper.properties +1 -1
- package/gradle.properties +15 -0
- package/package.json +2 -2
- package/app/src/main/java/io/appium/settings/location/LocationFactory.java +0 -63
package/README.md
CHANGED
|
@@ -115,11 +115,23 @@ You can set the [Locale](https://developer.android.com/reference/java/util/Local
|
|
|
115
115
|
You can retrieve the current geo location by executing:
|
|
116
116
|
|
|
117
117
|
```shell
|
|
118
|
-
$ adb shell am broadcast -a io.appium.settings.location -n io.appium.settings/.receivers.LocationInfoReceiver
|
|
118
|
+
$ adb shell am broadcast -a io.appium.settings.location -n io.appium.settings/.receivers.LocationInfoReceiver --ez forceUpdate false
|
|
119
119
|
```
|
|
120
120
|
|
|
121
121
|
The first value in the returned `data` string is the current latitude, the second is the longitude and the last one is the altitude. An empty string is returned if the data cannot be retrieved (more details on the failure cause can be found in the logcat output).
|
|
122
122
|
|
|
123
|
+
Since version 3.6.0 it is also possible to provide `forceUpdate` boolean argument. If it is set to
|
|
124
|
+
`true` then GPS cache refresh request is going to be send asynchronously every time when the
|
|
125
|
+
current location is requested. By default the cached location value is returned instead.
|
|
126
|
+
|
|
127
|
+
_Note_
|
|
128
|
+
|
|
129
|
+
The forced GPS cache refresh feature only works if the device under test has
|
|
130
|
+
Google Play Services installed. In case the vanilla LocationManager is used the device API level must be at
|
|
131
|
+
version 30 (Android R) or higher. If none of the conditions above is satisfied then enabling of the `forceUpdate`
|
|
132
|
+
option would have no effect.
|
|
133
|
+
|
|
134
|
+
|
|
123
135
|
## Setting Mock Locations
|
|
124
136
|
|
|
125
137
|
Start sending scheduled updates (every 2s) for mock location with the specified values by executing:
|
|
@@ -134,6 +146,12 @@ $ adb shell am startservice --user 0 -n io.appium.settings/.LocationService --es
|
|
|
134
146
|
Running the command again stops sending the previously specified location and starts sending updates for the
|
|
135
147
|
new mock location.
|
|
136
148
|
|
|
149
|
+
Additionally the service allows to provide the following optional parameters to the mocked
|
|
150
|
+
location:
|
|
151
|
+
|
|
152
|
+
- `speed`: the speed, in meters/second over ground. A float value greater than zero is acceptable.
|
|
153
|
+
- `bearing`: the bearing, in degrees. Bearing is the horizontal direction of travel of this device, and is not related to the device orientation. The input will be wrapped into the range (0.0, 360.0]
|
|
154
|
+
|
|
137
155
|
Stop sending new mocklocations and clean up everything (remove the mock location providers) by executing:
|
|
138
156
|
```shell
|
|
139
157
|
$ adb shell am stopservice io.appium.settings/.LocationService
|
|
@@ -280,6 +298,23 @@ The example of the resulting data:
|
|
|
280
298
|
```
|
|
281
299
|
|
|
282
300
|
|
|
301
|
+
## Media Scanning
|
|
302
|
+
|
|
303
|
+
Since version 3.5 Appium Settings supports broadcast messages handling
|
|
304
|
+
that performs media scanning in response to `io.appium.settings.scan_media`
|
|
305
|
+
intent. This was done due to `android.intent.action.MEDIA_SCANNER_SCAN_FILE` deprecation
|
|
306
|
+
since Android API version 30. To scan the given file or folder for media data simply run:
|
|
307
|
+
|
|
308
|
+
```bash
|
|
309
|
+
$ adb shell am broadcast -a io.appium.settings.scan_media --es path /sdcard/media
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
This command will _recursively_ scan all files inside of `/sdcard/media` folder
|
|
313
|
+
and add them to the media library if their MIME types are supported. If the
|
|
314
|
+
file/folder in _path_ does not exist/is not readable or is not provided then an
|
|
315
|
+
error will be returned and the corresponding log message would be written into logs.
|
|
316
|
+
|
|
317
|
+
|
|
283
318
|
## Notes:
|
|
284
319
|
|
|
285
320
|
* You have to specify the receiver class if the app has never been executed before:
|
|
Binary file
|
package/app/build.gradle
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
apply plugin: 'com.android.application'
|
|
2
2
|
|
|
3
3
|
android {
|
|
4
|
-
compileSdkVersion
|
|
5
|
-
buildToolsVersion '28.0.3'
|
|
4
|
+
compileSdkVersion 30
|
|
6
5
|
|
|
7
6
|
defaultConfig {
|
|
8
7
|
minSdkVersion 18
|
|
9
|
-
targetSdkVersion
|
|
10
|
-
versionCode
|
|
11
|
-
versionName "
|
|
8
|
+
targetSdkVersion 30
|
|
9
|
+
versionCode 33
|
|
10
|
+
versionName "4.0.0"
|
|
12
11
|
applicationId "io.appium.settings"
|
|
13
12
|
}
|
|
14
13
|
|
|
@@ -28,7 +27,7 @@ android {
|
|
|
28
27
|
}
|
|
29
28
|
|
|
30
29
|
dependencies {
|
|
31
|
-
implementation 'com.google.android.gms:play-services-location:
|
|
30
|
+
implementation 'com.google.android.gms:play-services-location:19.0.0'
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
static def renameAPK(variant) {
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
package="io.appium.settings">
|
|
5
5
|
|
|
6
6
|
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
|
|
7
|
+
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
|
7
8
|
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
|
8
9
|
<uses-permission android:name="android.permission.INTERNET" />
|
|
9
10
|
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
|
@@ -15,6 +16,7 @@
|
|
|
15
16
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
|
16
17
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
|
17
18
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
|
19
|
+
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
|
18
20
|
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"
|
|
19
21
|
tools:ignore="MockLocation,ProtectedPermissions" />
|
|
20
22
|
<uses-permission android:name="android.permission.SET_ANIMATION_SCALE"
|
|
@@ -132,5 +134,8 @@
|
|
|
132
134
|
<receiver android:name=".receivers.SmsReader"
|
|
133
135
|
android:exported="true"
|
|
134
136
|
tools:ignore="ExportedReceiver" />
|
|
137
|
+
<receiver android:name=".receivers.MediaScannerReceiver"
|
|
138
|
+
android:exported="true"
|
|
139
|
+
tools:ignore="ExportedReceiver" />
|
|
135
140
|
</application>
|
|
136
141
|
</manifest>
|
|
@@ -52,9 +52,11 @@ public class ForegroundService extends Service {
|
|
|
52
52
|
private void startForegroundService() {
|
|
53
53
|
startForeground(NotificationHelpers.APPIUM_NOTIFICATION_IDENTIFIER,
|
|
54
54
|
NotificationHelpers.getNotification(this));
|
|
55
|
+
LocationTracker.getInstance().start(this);
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
private void stopForegroundService() {
|
|
59
|
+
LocationTracker.getInstance().stop();
|
|
58
60
|
stopForeground(true);
|
|
59
61
|
stopSelf();
|
|
60
62
|
}
|
|
@@ -20,7 +20,6 @@ import android.app.Service;
|
|
|
20
20
|
import android.content.Context;
|
|
21
21
|
import android.content.Intent;
|
|
22
22
|
import android.content.pm.PackageManager;
|
|
23
|
-
import android.location.Criteria;
|
|
24
23
|
import android.location.Location;
|
|
25
24
|
import android.location.LocationManager;
|
|
26
25
|
import android.location.LocationProvider;
|
|
@@ -28,7 +27,8 @@ import android.os.Build;
|
|
|
28
27
|
import android.os.IBinder;
|
|
29
28
|
import android.util.Log;
|
|
30
29
|
|
|
31
|
-
import
|
|
30
|
+
import androidx.annotation.Nullable;
|
|
31
|
+
|
|
32
32
|
import com.google.android.gms.location.FusedLocationProviderClient;
|
|
33
33
|
import com.google.android.gms.location.LocationServices;
|
|
34
34
|
|
|
@@ -40,22 +40,16 @@ import java.util.TimerTask;
|
|
|
40
40
|
import io.appium.settings.helpers.NotificationHelpers;
|
|
41
41
|
import io.appium.settings.helpers.PlayServicesHelpers;
|
|
42
42
|
import io.appium.settings.location.FusedLocationProvider;
|
|
43
|
-
import io.appium.settings.location.
|
|
43
|
+
import io.appium.settings.location.LocationBuilder;
|
|
44
44
|
import io.appium.settings.location.LocationManagerProvider;
|
|
45
45
|
import io.appium.settings.location.MockLocationProvider;
|
|
46
46
|
|
|
47
47
|
public class LocationService extends Service {
|
|
48
48
|
private static final String TAG = "MOCKED LOCATION SERVICE";
|
|
49
49
|
|
|
50
|
-
private static final String LONGITUDE_PARAMETER_KEY = "longitude";
|
|
51
|
-
private static final String LATITUDE_PARAMETER_KEY = "latitude";
|
|
52
|
-
private static final String ALTITUDE_PARAMETER_KEY = "altitude";
|
|
53
|
-
private static final String SPEED_PARAMETER_KEY = "speed";
|
|
54
|
-
|
|
55
50
|
private static final long UPDATE_INTERVAL_MS = 2000L;
|
|
56
51
|
|
|
57
52
|
private final List<MockLocationProvider> mockLocationProviders = new LinkedList<>();
|
|
58
|
-
private final LocationFactory locationFactory = new LocationFactory();
|
|
59
53
|
private final Timer locationUpdatesTimer = new Timer();
|
|
60
54
|
private TimerTask locationUpdateTask;
|
|
61
55
|
|
|
@@ -106,9 +100,7 @@ public class LocationService extends Service {
|
|
|
106
100
|
}
|
|
107
101
|
Log.i(TAG, "INTENT " + intent.getExtras());
|
|
108
102
|
|
|
109
|
-
|
|
110
|
-
updateMockLocationFactory(intent);
|
|
111
|
-
scheduleLocationUpdate();
|
|
103
|
+
scheduleLocationUpdate(intent);
|
|
112
104
|
}
|
|
113
105
|
|
|
114
106
|
private void enableLocationProviders() {
|
|
@@ -145,7 +137,7 @@ public class LocationService extends Service {
|
|
|
145
137
|
Log.d(TAG, String.format("Created mock providers: %s", mockLocationProviders.toString()));
|
|
146
138
|
}
|
|
147
139
|
|
|
148
|
-
private void scheduleLocationUpdate() {
|
|
140
|
+
private void scheduleLocationUpdate(final Intent intent) {
|
|
149
141
|
Log.i(TAG, "Scheduling mock location updates");
|
|
150
142
|
|
|
151
143
|
// If we run 'startservice' again we should schedule an update right away to avoid a delay
|
|
@@ -157,7 +149,7 @@ public class LocationService extends Service {
|
|
|
157
149
|
@Override
|
|
158
150
|
public void run() {
|
|
159
151
|
for (MockLocationProvider mockLocationProvider : mockLocationProviders) {
|
|
160
|
-
Location location =
|
|
152
|
+
Location location = LocationBuilder.buildFromIntent(intent, mockLocationProvider.getProviderName());
|
|
161
153
|
Log.d(TAG, String.format("Setting location of '%s' to '%s'", mockLocationProvider.getProviderName(), location));
|
|
162
154
|
try {
|
|
163
155
|
mockLocationProvider.setLocation(location);
|
|
@@ -172,47 +164,6 @@ public class LocationService extends Service {
|
|
|
172
164
|
locationUpdatesTimer.schedule(locationUpdateTask, 0, UPDATE_INTERVAL_MS);
|
|
173
165
|
}
|
|
174
166
|
|
|
175
|
-
private void updateMockLocationFactory(Intent intent) {
|
|
176
|
-
double longitude;
|
|
177
|
-
try {
|
|
178
|
-
longitude = Double.valueOf(intent.getStringExtra("longitude"));
|
|
179
|
-
} catch (NumberFormatException e) {
|
|
180
|
-
throw new IllegalArgumentException(
|
|
181
|
-
String.format("longitude should be a valid number. '%s' is given instead",
|
|
182
|
-
intent.getStringExtra(LONGITUDE_PARAMETER_KEY)));
|
|
183
|
-
}
|
|
184
|
-
double latitude;
|
|
185
|
-
try {
|
|
186
|
-
latitude = Double.valueOf(intent.getStringExtra("latitude"));
|
|
187
|
-
} catch (NumberFormatException e) {
|
|
188
|
-
throw new IllegalArgumentException(
|
|
189
|
-
String.format("latitude should be a valid number. '%s' is given instead",
|
|
190
|
-
intent.getStringExtra(LATITUDE_PARAMETER_KEY)));
|
|
191
|
-
}
|
|
192
|
-
double altitude = 0.0;
|
|
193
|
-
try {
|
|
194
|
-
if (intent.hasExtra(ALTITUDE_PARAMETER_KEY)) {
|
|
195
|
-
altitude = Double.valueOf(intent.getStringExtra(ALTITUDE_PARAMETER_KEY));
|
|
196
|
-
}
|
|
197
|
-
} catch (NumberFormatException e) {
|
|
198
|
-
Log.e(TAG, String.format("altitude should be a valid number. '%s' is given instead",
|
|
199
|
-
intent.getStringExtra(ALTITUDE_PARAMETER_KEY)));
|
|
200
|
-
}
|
|
201
|
-
try {
|
|
202
|
-
if (intent.hasExtra(SPEED_PARAMETER_KEY)) {
|
|
203
|
-
float speed = Float.valueOf(intent.getStringExtra(SPEED_PARAMETER_KEY));
|
|
204
|
-
|
|
205
|
-
locationFactory.setLocation(latitude, longitude, altitude, speed);
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
} catch (NumberFormatException e) {
|
|
209
|
-
Log.e(TAG, String.format("speed should be a valid number larger then 0.0. '%s' is given instead",
|
|
210
|
-
intent.getStringExtra(SPEED_PARAMETER_KEY)));
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
locationFactory.setLocation(latitude, longitude, altitude);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
167
|
private List<MockLocationProvider> createMockProviders(LocationManager locationManager) {
|
|
217
168
|
List<String> providers = locationManager.getAllProviders();
|
|
218
169
|
List<MockLocationProvider> mockProviders = new LinkedList<>();
|
|
@@ -231,9 +182,10 @@ public class LocationService extends Service {
|
|
|
231
182
|
return mockProviders;
|
|
232
183
|
}
|
|
233
184
|
|
|
185
|
+
@Nullable
|
|
234
186
|
private MockLocationProvider createLocationManagerMockProvider(LocationManager locationManager, String providerName) {
|
|
235
187
|
LocationProvider provider = locationManager.getProvider(providerName);
|
|
236
|
-
return createLocationManagerMockProvider(locationManager, provider);
|
|
188
|
+
return provider == null ? null : createLocationManagerMockProvider(locationManager, provider);
|
|
237
189
|
}
|
|
238
190
|
|
|
239
191
|
private MockLocationProvider createLocationManagerMockProvider(LocationManager locationManager, LocationProvider locationProvider) {
|
|
@@ -251,11 +203,8 @@ public class LocationService extends Service {
|
|
|
251
203
|
}
|
|
252
204
|
|
|
253
205
|
private FusedLocationProvider createFusedLocationProvider() {
|
|
254
|
-
GoogleApiClient googleApiClient = new GoogleApiClient.Builder(this)
|
|
255
|
-
.addApi(LocationServices.API)
|
|
256
|
-
.build();
|
|
257
206
|
FusedLocationProviderClient locationProviderClient = LocationServices.getFusedLocationProviderClient(this);
|
|
258
|
-
return new FusedLocationProvider(
|
|
207
|
+
return new FusedLocationProvider(locationProviderClient, this);
|
|
259
208
|
}
|
|
260
209
|
|
|
261
210
|
private void finishForegroundSetup() {
|
|
@@ -20,32 +20,44 @@ import android.content.Context;
|
|
|
20
20
|
import android.location.Location;
|
|
21
21
|
import android.location.LocationListener;
|
|
22
22
|
import android.location.LocationManager;
|
|
23
|
+
import android.os.Build;
|
|
23
24
|
import android.os.Bundle;
|
|
24
|
-
import android.
|
|
25
|
-
import android.support.annotation.Nullable;
|
|
25
|
+
import android.os.Looper;
|
|
26
26
|
import android.util.Log;
|
|
27
27
|
|
|
28
|
-
import com.google.android.gms.
|
|
29
|
-
import com.google.android.gms.
|
|
28
|
+
import com.google.android.gms.location.FusedLocationProviderClient;
|
|
29
|
+
import com.google.android.gms.location.LocationCallback;
|
|
30
30
|
import com.google.android.gms.location.LocationRequest;
|
|
31
|
+
import com.google.android.gms.location.LocationResult;
|
|
31
32
|
import com.google.android.gms.location.LocationServices;
|
|
33
|
+
import com.google.android.gms.tasks.CancellationTokenSource;
|
|
32
34
|
|
|
33
35
|
import io.appium.settings.helpers.PlayServicesHelpers;
|
|
34
36
|
|
|
35
37
|
import static android.content.Context.LOCATION_SERVICE;
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
import androidx.annotation.NonNull;
|
|
40
|
+
import androidx.annotation.Nullable;
|
|
41
|
+
|
|
42
|
+
import java.util.concurrent.atomic.AtomicBoolean;
|
|
43
|
+
|
|
44
|
+
public class LocationTracker implements LocationListener {
|
|
40
45
|
private static final String TAG = LocationTracker.class.getSimpleName();
|
|
41
|
-
private static final long MIN_DISTANCE_CHANGE_FOR_UPDATES =
|
|
42
|
-
private static final long
|
|
43
|
-
private static final long
|
|
46
|
+
private static final long MIN_DISTANCE_CHANGE_FOR_UPDATES = 1; // 1 meter
|
|
47
|
+
private static final long LOCATION_UPDATES_INTERVAL_MS = 1000 * 60; // 1 minute
|
|
48
|
+
private static final long FAST_INTERVAL_MS = 5000;
|
|
44
49
|
|
|
45
50
|
private volatile LocationManager mLocationManager;
|
|
46
|
-
private volatile
|
|
51
|
+
private volatile FusedLocationProviderClient mFusedLocationProviderClient;
|
|
52
|
+
private final LocationCallback locationCallback = new LocationCallback() {
|
|
53
|
+
@Override
|
|
54
|
+
public void onLocationResult(@NonNull LocationResult locationResult) {
|
|
55
|
+
Log.d(TAG, "Got a location update from Play Services");
|
|
56
|
+
mLocation = locationResult.getLastLocation();
|
|
57
|
+
}
|
|
58
|
+
};
|
|
47
59
|
private volatile Location mLocation;
|
|
48
|
-
private
|
|
60
|
+
private final AtomicBoolean isStarted = new AtomicBoolean(false);
|
|
49
61
|
private String mLocationProvider;
|
|
50
62
|
|
|
51
63
|
private static LocationTracker instance = null;
|
|
@@ -60,96 +72,84 @@ public class LocationTracker implements GoogleApiClient.ConnectionCallbacks,
|
|
|
60
72
|
return instance;
|
|
61
73
|
}
|
|
62
74
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (location != null) {
|
|
66
|
-
Log.d(TAG, "Got an updated location");
|
|
67
|
-
mLocation = location;
|
|
68
|
-
}
|
|
75
|
+
public boolean isRunning() {
|
|
76
|
+
return isStarted.get();
|
|
69
77
|
}
|
|
70
78
|
|
|
71
|
-
|
|
72
|
-
|
|
79
|
+
private void setIsRunning(boolean value) {
|
|
80
|
+
isStarted.set(value);
|
|
73
81
|
}
|
|
74
82
|
|
|
75
83
|
@Override
|
|
76
|
-
public void
|
|
77
|
-
|
|
84
|
+
public void onLocationChanged(@Nullable Location location) {
|
|
85
|
+
if (location == null) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
78
88
|
|
|
79
|
-
|
|
80
|
-
|
|
89
|
+
Log.d(TAG, "Got a location update from Location Manager");
|
|
90
|
+
mLocation = location;
|
|
81
91
|
}
|
|
82
92
|
|
|
83
93
|
@Override
|
|
84
|
-
public void
|
|
85
|
-
if (mGoogleApiClient == null) {
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
if (!mGoogleApiClient.isConnected()) {
|
|
89
|
-
// This should never happen, but it's happening on some phones
|
|
90
|
-
Log.e(TAG, "Google API is still connecting being inside onConnected callback");
|
|
91
|
-
mGoogleApiClient = null;
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
LocationRequest locationRequest = new LocationRequest();
|
|
95
|
-
locationRequest.setPriority(LocationRequest.PRIORITY_LOW_POWER);
|
|
96
|
-
locationRequest.setInterval(LOCATION_UPDATES_INTERVAL);
|
|
97
|
-
locationRequest.setFastestInterval(FAST_INTERVAL);
|
|
98
|
-
try {
|
|
99
|
-
//noinspection deprecation
|
|
100
|
-
LocationServices.FusedLocationApi
|
|
101
|
-
.requestLocationUpdates(mGoogleApiClient, locationRequest, this);
|
|
102
|
-
Log.d(TAG, "Google Play Services location provider is connected");
|
|
103
|
-
return;
|
|
104
|
-
} catch (SecurityException e) {
|
|
105
|
-
Log.e(TAG, "Appium Settings has no access to location permission", e);
|
|
106
|
-
} catch (Exception e) {
|
|
107
|
-
Log.e(TAG, "Cannot connect to Google location service", e);
|
|
108
|
-
}
|
|
109
|
-
stopLocationUpdatesWithPlayServices();
|
|
94
|
+
public void onProviderDisabled(@NonNull String provider) {
|
|
110
95
|
}
|
|
111
96
|
|
|
112
97
|
@Override
|
|
113
|
-
public void
|
|
98
|
+
public void onProviderEnabled(@NonNull String provider) {
|
|
114
99
|
}
|
|
115
100
|
|
|
116
101
|
@Override
|
|
117
|
-
public void
|
|
118
|
-
if (mGoogleApiClient == null) {
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
Log.e(TAG, String.format("Google Play Services location provider has failed to connect (code %s)",
|
|
123
|
-
connectionResult.toString()));
|
|
124
|
-
stopLocationUpdatesWithPlayServices();
|
|
102
|
+
public void onStatusChanged(String provider, int status, Bundle extras) {
|
|
125
103
|
}
|
|
126
104
|
|
|
127
105
|
synchronized void start(Context context) {
|
|
128
|
-
if (
|
|
106
|
+
if (isRunning()) {
|
|
129
107
|
return;
|
|
130
108
|
}
|
|
131
|
-
|
|
109
|
+
setIsRunning(true);
|
|
132
110
|
|
|
133
|
-
|
|
134
|
-
if (isPlayServicesAvailable) {
|
|
111
|
+
if (PlayServicesHelpers.isAvailable(context)) {
|
|
135
112
|
initializePlayServices(context);
|
|
136
113
|
} else {
|
|
137
114
|
initializeLocationManager(context);
|
|
138
115
|
}
|
|
139
116
|
}
|
|
140
117
|
|
|
118
|
+
synchronized void stop() {
|
|
119
|
+
if (!isRunning()) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
stopLocationUpdatesWithPlayServices();
|
|
125
|
+
stopLocationUpdatesWithoutPlayServices();
|
|
126
|
+
} finally {
|
|
127
|
+
setIsRunning(false);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
141
131
|
private void initializePlayServices(Context context) {
|
|
142
|
-
if (
|
|
132
|
+
if (isFusedLocationProviderInitialized()) {
|
|
143
133
|
return;
|
|
144
134
|
}
|
|
145
135
|
|
|
146
136
|
Log.d(TAG, "Configuring location provider for Google Play Services");
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
.
|
|
150
|
-
.
|
|
151
|
-
.
|
|
152
|
-
|
|
137
|
+
mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context);
|
|
138
|
+
LocationRequest locationRequest = LocationRequest.create()
|
|
139
|
+
.setPriority(LocationRequest.PRIORITY_LOW_POWER)
|
|
140
|
+
.setInterval(LOCATION_UPDATES_INTERVAL_MS)
|
|
141
|
+
.setFastestInterval(FAST_INTERVAL_MS);
|
|
142
|
+
try {
|
|
143
|
+
mFusedLocationProviderClient.requestLocationUpdates(
|
|
144
|
+
locationRequest, locationCallback, Looper.getMainLooper());
|
|
145
|
+
Log.d(TAG, "Google Play Services location provider is connected");
|
|
146
|
+
return;
|
|
147
|
+
} catch (SecurityException e) {
|
|
148
|
+
Log.e(TAG, "Appium Settings has no access to location permission", e);
|
|
149
|
+
} catch (Exception e) {
|
|
150
|
+
Log.e(TAG, "Cannot connect to Google location service", e);
|
|
151
|
+
}
|
|
152
|
+
stopLocationUpdatesWithPlayServices();
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
private void initializeLocationManager(Context context) {
|
|
@@ -168,17 +168,16 @@ public class LocationTracker implements GoogleApiClient.ConnectionCallbacks,
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
private void stopLocationUpdatesWithPlayServices() {
|
|
171
|
-
if (
|
|
171
|
+
if (!isFusedLocationProviderInitialized()) {
|
|
172
172
|
return;
|
|
173
173
|
}
|
|
174
174
|
|
|
175
175
|
Log.d(TAG, "Stopping Google Play Services location provider");
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
mGoogleApiClient.disconnect();
|
|
176
|
+
mFusedLocationProviderClient.removeLocationUpdates(locationCallback);
|
|
177
|
+
if (mFusedLocationProviderClient.asGoogleApiClient().isConnected()) {
|
|
178
|
+
mFusedLocationProviderClient.asGoogleApiClient().disconnect();
|
|
180
179
|
}
|
|
181
|
-
|
|
180
|
+
mFusedLocationProviderClient = null;
|
|
182
181
|
}
|
|
183
182
|
|
|
184
183
|
private void startLocationUpdatesWithoutPlayServices() {
|
|
@@ -199,7 +198,7 @@ public class LocationTracker implements GoogleApiClient.ConnectionCallbacks,
|
|
|
199
198
|
try {
|
|
200
199
|
mLocationManager.requestLocationUpdates(
|
|
201
200
|
LocationManager.GPS_PROVIDER,
|
|
202
|
-
|
|
201
|
+
LOCATION_UPDATES_INTERVAL_MS,
|
|
203
202
|
MIN_DISTANCE_CHANGE_FOR_UPDATES, this);
|
|
204
203
|
mLocationProvider = LocationManager.GPS_PROVIDER;
|
|
205
204
|
Log.d(TAG, "GPS location provider is enabled. Getting FINE location");
|
|
@@ -211,7 +210,7 @@ public class LocationTracker implements GoogleApiClient.ConnectionCallbacks,
|
|
|
211
210
|
try {
|
|
212
211
|
mLocationManager.requestLocationUpdates(
|
|
213
212
|
LocationManager.NETWORK_PROVIDER,
|
|
214
|
-
|
|
213
|
+
LOCATION_UPDATES_INTERVAL_MS,
|
|
215
214
|
MIN_DISTANCE_CHANGE_FOR_UPDATES, this);
|
|
216
215
|
mLocationProvider = LocationManager.NETWORK_PROVIDER;
|
|
217
216
|
Log.d(TAG, "NETWORK location provider is enabled. Getting COARSE location");
|
|
@@ -230,8 +229,8 @@ public class LocationTracker implements GoogleApiClient.ConnectionCallbacks,
|
|
|
230
229
|
mLocationManager = null;
|
|
231
230
|
}
|
|
232
231
|
|
|
233
|
-
private boolean
|
|
234
|
-
return
|
|
232
|
+
private boolean isFusedLocationProviderInitialized() {
|
|
233
|
+
return mFusedLocationProviderClient != null;
|
|
235
234
|
}
|
|
236
235
|
|
|
237
236
|
private boolean isLocationManagerConnected() {
|
|
@@ -239,52 +238,76 @@ public class LocationTracker implements GoogleApiClient.ConnectionCallbacks,
|
|
|
239
238
|
}
|
|
240
239
|
|
|
241
240
|
@Nullable
|
|
242
|
-
|
|
241
|
+
private Location getCachedLocation() {
|
|
243
242
|
if (mLocation != null) {
|
|
243
|
+
Log.d(TAG, "The cached location has been successfully retrieved");
|
|
244
244
|
return mLocation;
|
|
245
245
|
}
|
|
246
|
+
Log.d(TAG, "The cached location has not been retrieved");
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
246
249
|
|
|
247
|
-
|
|
248
|
-
|
|
250
|
+
public synchronized void forceLocationUpdate(Context context) {
|
|
251
|
+
if (!isRunning()) {
|
|
252
|
+
Log.e(TAG, "The location tracker is not running");
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
249
255
|
|
|
250
|
-
if (
|
|
256
|
+
if (isFusedLocationProviderInitialized()) {
|
|
251
257
|
try {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
258
|
+
mFusedLocationProviderClient.getCurrentLocation(
|
|
259
|
+
LocationRequest.PRIORITY_HIGH_ACCURACY, new CancellationTokenSource().getToken())
|
|
260
|
+
.addOnCompleteListener(t -> {
|
|
261
|
+
if (t.isSuccessful()) {
|
|
262
|
+
mLocation = t.getResult();
|
|
263
|
+
Log.d(TAG, "The current location has been successfully retrieved from " +
|
|
264
|
+
"Play Services");
|
|
265
|
+
} else {
|
|
266
|
+
Log.w(TAG, "Failed to retrieve the current location from Play Services",
|
|
267
|
+
t.getException());
|
|
268
|
+
}
|
|
269
|
+
});
|
|
265
270
|
} catch (SecurityException e) {
|
|
266
271
|
Log.e(TAG, "Appium Settings has no access to location permission", e);
|
|
267
272
|
}
|
|
273
|
+
} else if (isLocationManagerConnected() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
274
|
+
try {
|
|
275
|
+
mLocationManager.getCurrentLocation(mLocationProvider, null,
|
|
276
|
+
context.getMainExecutor(), location -> {
|
|
277
|
+
mLocation = location;
|
|
278
|
+
Log.d(TAG, "The current location has been successfully retrieved " +
|
|
279
|
+
"from Location Manager");
|
|
280
|
+
});
|
|
281
|
+
} catch (SecurityException e) {
|
|
282
|
+
Log.e(TAG, "Appium Settings has no access to location permission", e);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
@Nullable
|
|
288
|
+
public synchronized Location getLocation(Context context) {
|
|
289
|
+
if (!isRunning()) {
|
|
290
|
+
Log.e(TAG, "The location tracker is not running");
|
|
291
|
+
return null;
|
|
268
292
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
}
|
|
293
|
+
|
|
294
|
+
if (isFusedLocationProviderInitialized()) {
|
|
295
|
+
Location location = getCachedLocation();
|
|
296
|
+
if (location != null) {
|
|
297
|
+
// If Play services worked, make sure location manager is disabled
|
|
298
|
+
stopLocationUpdatesWithoutPlayServices();
|
|
299
|
+
return location;
|
|
277
300
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
mLocationProvider), e);
|
|
285
|
-
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// If Play services didn't work, try location manager
|
|
304
|
+
try {
|
|
305
|
+
if (!isLocationManagerConnected()) {
|
|
306
|
+
initializeLocationManager(context);
|
|
286
307
|
}
|
|
308
|
+
} catch (SecurityException e) {
|
|
309
|
+
Log.e(TAG, "Appium Settings has no access to location permission", e);
|
|
287
310
|
}
|
|
288
|
-
return null;
|
|
311
|
+
return isLocationManagerConnected() ? getCachedLocation() : null;
|
|
289
312
|
}
|
|
290
313
|
}
|
|
@@ -20,8 +20,10 @@ import android.content.Intent;
|
|
|
20
20
|
import android.os.IBinder;
|
|
21
21
|
import android.service.notification.NotificationListenerService;
|
|
22
22
|
import android.service.notification.StatusBarNotification;
|
|
23
|
-
import android.support.annotation.Nullable;
|
|
24
23
|
import android.util.Log;
|
|
24
|
+
|
|
25
|
+
import androidx.annotation.Nullable;
|
|
26
|
+
|
|
25
27
|
import io.appium.settings.notifications.StoredNotification;
|
|
26
28
|
import io.appium.settings.notifications.StoredNotifications;
|
|
27
29
|
|
|
@@ -24,7 +24,7 @@ import android.os.Bundle;
|
|
|
24
24
|
import android.os.Handler;
|
|
25
25
|
import android.util.Log;
|
|
26
26
|
|
|
27
|
-
import java.util.
|
|
27
|
+
import java.util.Arrays;
|
|
28
28
|
import java.util.List;
|
|
29
29
|
|
|
30
30
|
import io.appium.settings.receivers.AnimationSettingReceiver;
|
|
@@ -34,6 +34,7 @@ import io.appium.settings.receivers.DataConnectionSettingReceiver;
|
|
|
34
34
|
import io.appium.settings.receivers.HasAction;
|
|
35
35
|
import io.appium.settings.receivers.LocaleSettingReceiver;
|
|
36
36
|
import io.appium.settings.receivers.LocationInfoReceiver;
|
|
37
|
+
import io.appium.settings.receivers.MediaScannerReceiver;
|
|
37
38
|
import io.appium.settings.receivers.NotificationsReceiver;
|
|
38
39
|
import io.appium.settings.receivers.SmsReader;
|
|
39
40
|
import io.appium.settings.receivers.UnpairBluetoothDevicesReceiver;
|
|
@@ -48,33 +49,30 @@ public class Settings extends Activity {
|
|
|
48
49
|
setContentView(R.layout.main);
|
|
49
50
|
Log.d(TAG, "Entering the app");
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
registerSettingsReceivers(receiverClasses);
|
|
52
|
+
registerSettingsReceivers(Arrays.asList(
|
|
53
|
+
WiFiConnectionSettingReceiver.class,
|
|
54
|
+
AnimationSettingReceiver.class,
|
|
55
|
+
DataConnectionSettingReceiver.class,
|
|
56
|
+
LocaleSettingReceiver.class,
|
|
57
|
+
LocationInfoReceiver.class,
|
|
58
|
+
ClipboardReceiver.class,
|
|
59
|
+
BluetoothConnectionSettingReceiver.class,
|
|
60
|
+
UnpairBluetoothDevicesReceiver.class,
|
|
61
|
+
NotificationsReceiver.class,
|
|
62
|
+
SmsReader.class,
|
|
63
|
+
MediaScannerReceiver.class
|
|
64
|
+
));
|
|
65
65
|
|
|
66
66
|
// https://developer.android.com/about/versions/oreo/background-location-limits
|
|
67
67
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
68
68
|
startForegroundService(ForegroundService.getForegroundServiceIntent(Settings.this));
|
|
69
|
+
} else {
|
|
70
|
+
LocationTracker.getInstance().start(this);
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
Log.d(TAG, "Closing the app");
|
|
72
74
|
Handler handler = new Handler();
|
|
73
|
-
handler.postDelayed(
|
|
74
|
-
public void run() {
|
|
75
|
-
Settings.this.finish();
|
|
76
|
-
}
|
|
77
|
-
}, 1000);
|
|
75
|
+
handler.postDelayed(Settings.this::finish, 1000);
|
|
78
76
|
}
|
|
79
77
|
|
|
80
78
|
private void registerSettingsReceivers(List<Class<? extends BroadcastReceiver>> receiverClasses) {
|
package/app/src/main/java/io/appium/settings/handlers/BluetoothConnectionSettingHandler.java
CHANGED
|
@@ -16,9 +16,11 @@
|
|
|
16
16
|
|
|
17
17
|
package io.appium.settings.handlers;
|
|
18
18
|
|
|
19
|
+
import android.annotation.SuppressLint;
|
|
19
20
|
import android.bluetooth.BluetoothAdapter;
|
|
20
21
|
import android.content.Context;
|
|
21
22
|
|
|
23
|
+
@SuppressLint("MissingPermission")
|
|
22
24
|
public class BluetoothConnectionSettingHandler extends AbstractSettingHandler {
|
|
23
25
|
|
|
24
26
|
private BluetoothAdapter bluetoothAdapter;
|
|
@@ -22,13 +22,14 @@ import android.app.NotificationManager;
|
|
|
22
22
|
import android.content.Context;
|
|
23
23
|
import android.graphics.BitmapFactory;
|
|
24
24
|
import android.os.Build;
|
|
25
|
-
import android.support.annotation.RequiresApi;
|
|
26
|
-
import android.support.v4.app.NotificationCompat;
|
|
27
25
|
|
|
28
26
|
import io.appium.settings.R;
|
|
29
27
|
|
|
30
28
|
import static android.content.Context.NOTIFICATION_SERVICE;
|
|
31
29
|
|
|
30
|
+
import androidx.annotation.RequiresApi;
|
|
31
|
+
import androidx.core.app.NotificationCompat;
|
|
32
|
+
|
|
32
33
|
public class NotificationHelpers {
|
|
33
34
|
public static final int APPIUM_NOTIFICATION_IDENTIFIER = 1;
|
|
34
35
|
private static final String CHANNEL_ID = "main_channel";
|
|
@@ -22,11 +22,12 @@ import android.content.Context;
|
|
|
22
22
|
import android.location.Location;
|
|
23
23
|
import android.util.Log;
|
|
24
24
|
|
|
25
|
-
import com.google.android.gms.common.api.GoogleApiClient;
|
|
26
25
|
import com.google.android.gms.location.FusedLocationProviderClient;
|
|
27
26
|
|
|
28
|
-
import static
|
|
29
|
-
import static
|
|
27
|
+
import static androidx.core.content.PermissionChecker.PERMISSION_GRANTED;
|
|
28
|
+
import static androidx.core.content.PermissionChecker.checkSelfPermission;
|
|
29
|
+
|
|
30
|
+
import androidx.annotation.NonNull;
|
|
30
31
|
|
|
31
32
|
public class FusedLocationProvider implements MockLocationProvider {
|
|
32
33
|
|
|
@@ -34,12 +35,10 @@ public class FusedLocationProvider implements MockLocationProvider {
|
|
|
34
35
|
|
|
35
36
|
private final static String PROVIDER_NAME = "fused";
|
|
36
37
|
|
|
37
|
-
private final GoogleApiClient googleApiClient;
|
|
38
38
|
private final FusedLocationProviderClient fusedLocationProviderClient;
|
|
39
39
|
private final Context context;
|
|
40
40
|
|
|
41
|
-
public FusedLocationProvider(
|
|
42
|
-
this.googleApiClient = googleApiClient;
|
|
41
|
+
public FusedLocationProvider(FusedLocationProviderClient fusedLocationProviderClient, Context context) {
|
|
43
42
|
this.fusedLocationProviderClient = fusedLocationProviderClient;
|
|
44
43
|
this.context = context;
|
|
45
44
|
}
|
|
@@ -55,7 +54,7 @@ public class FusedLocationProvider implements MockLocationProvider {
|
|
|
55
54
|
if (!hasPermissions()) {
|
|
56
55
|
return;
|
|
57
56
|
}
|
|
58
|
-
if (!
|
|
57
|
+
if (!fusedLocationProviderClient.asGoogleApiClient().isConnected()) {
|
|
59
58
|
Log.d(TAG, "GoogleApiClient is not connected");
|
|
60
59
|
return;
|
|
61
60
|
}
|
|
@@ -68,7 +67,7 @@ public class FusedLocationProvider implements MockLocationProvider {
|
|
|
68
67
|
if (!hasPermissions()) {
|
|
69
68
|
return;
|
|
70
69
|
}
|
|
71
|
-
|
|
70
|
+
fusedLocationProviderClient.asGoogleApiClient().connect();
|
|
72
71
|
fusedLocationProviderClient.setMockMode(true);
|
|
73
72
|
}
|
|
74
73
|
|
|
@@ -79,10 +78,11 @@ public class FusedLocationProvider implements MockLocationProvider {
|
|
|
79
78
|
return;
|
|
80
79
|
}
|
|
81
80
|
fusedLocationProviderClient.setMockMode(false);
|
|
82
|
-
|
|
81
|
+
fusedLocationProviderClient.asGoogleApiClient().disconnect();
|
|
83
82
|
}
|
|
84
83
|
|
|
85
84
|
@Override
|
|
85
|
+
@NonNull
|
|
86
86
|
public String toString() {
|
|
87
87
|
return "FusedLocationProvider{" +
|
|
88
88
|
"name='" + PROVIDER_NAME + '\'' +
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2012-present Appium Committers
|
|
3
|
+
<p>
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
<p>
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
<p>
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
package io.appium.settings.location;
|
|
18
|
+
|
|
19
|
+
import android.content.Intent;
|
|
20
|
+
import android.location.Criteria;
|
|
21
|
+
import android.location.Location;
|
|
22
|
+
import android.os.SystemClock;
|
|
23
|
+
import android.util.Log;
|
|
24
|
+
|
|
25
|
+
import androidx.annotation.Nullable;
|
|
26
|
+
|
|
27
|
+
public class LocationBuilder {
|
|
28
|
+
|
|
29
|
+
private static final String TAG = "MOCKED LOCATION BUILDER";
|
|
30
|
+
private static final String LONGITUDE_PARAMETER_KEY = "longitude";
|
|
31
|
+
private static final String LATITUDE_PARAMETER_KEY = "latitude";
|
|
32
|
+
private static final String ALTITUDE_PARAMETER_KEY = "altitude";
|
|
33
|
+
private static final String SPEED_PARAMETER_KEY = "speed";
|
|
34
|
+
private static final String BEARING_PARAMETER_KEY = "bearing";
|
|
35
|
+
|
|
36
|
+
@Nullable
|
|
37
|
+
private static Double extractParam(Intent intent, String paramKey) {
|
|
38
|
+
Double value = null;
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
if (intent.hasExtra(paramKey)) {
|
|
42
|
+
value = Double.parseDouble(intent.getStringExtra(paramKey));
|
|
43
|
+
Log.i(TAG, String.format("Received parameter: %s, value: %s", paramKey, value));
|
|
44
|
+
}
|
|
45
|
+
} catch (NumberFormatException e) {
|
|
46
|
+
Log.e(TAG, String.format("%s should be a valid number. '%s' is given instead",
|
|
47
|
+
paramKey, intent.getStringExtra(paramKey)));
|
|
48
|
+
}
|
|
49
|
+
return value;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public static Location buildFromIntent(Intent intent, String providerName) {
|
|
53
|
+
Double longitude = extractParam(intent, LONGITUDE_PARAMETER_KEY);
|
|
54
|
+
Double latitude = extractParam(intent, LATITUDE_PARAMETER_KEY);
|
|
55
|
+
Double altitude = extractParam(intent, ALTITUDE_PARAMETER_KEY);
|
|
56
|
+
Double speed = extractParam(intent, SPEED_PARAMETER_KEY);
|
|
57
|
+
Double bearing = extractParam(intent, BEARING_PARAMETER_KEY);
|
|
58
|
+
Location location = new Location(providerName);
|
|
59
|
+
location.setAccuracy(Criteria.ACCURACY_FINE);
|
|
60
|
+
if (longitude != null) {
|
|
61
|
+
location.setLongitude(longitude);
|
|
62
|
+
}
|
|
63
|
+
if (latitude != null) {
|
|
64
|
+
location.setLatitude(latitude);
|
|
65
|
+
}
|
|
66
|
+
if (altitude != null) {
|
|
67
|
+
location.setAltitude(altitude);
|
|
68
|
+
}
|
|
69
|
+
if (speed != null) {
|
|
70
|
+
location.setSpeed(speed.floatValue());
|
|
71
|
+
}
|
|
72
|
+
if (bearing != null) {
|
|
73
|
+
location.setBearing(bearing.floatValue());
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
location.setTime(System.currentTimeMillis());
|
|
77
|
+
location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
|
|
78
|
+
return location;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -28,8 +28,9 @@ import java.util.Locale;
|
|
|
28
28
|
import io.appium.settings.LocationTracker;
|
|
29
29
|
|
|
30
30
|
public class LocationInfoReceiver extends BroadcastReceiver
|
|
31
|
-
|
|
31
|
+
implements HasAction {
|
|
32
32
|
private static final String TAG = LocationInfoReceiver.class.getSimpleName();
|
|
33
|
+
private static final String FORCE_UPDATE = "forceUpdate";
|
|
33
34
|
|
|
34
35
|
private static final String ACTION = "io.appium.settings.location";
|
|
35
36
|
|
|
@@ -41,7 +42,13 @@ public class LocationInfoReceiver extends BroadcastReceiver
|
|
|
41
42
|
@Override
|
|
42
43
|
public void onReceive(Context context, Intent intent) {
|
|
43
44
|
Log.d(TAG, "Getting current location");
|
|
44
|
-
|
|
45
|
+
LocationTracker tracker = LocationTracker.getInstance();
|
|
46
|
+
if (intent.hasExtra(FORCE_UPDATE)
|
|
47
|
+
&& intent.getBooleanExtra(FORCE_UPDATE, false)) {
|
|
48
|
+
Log.d(TAG, "Initiating forced location update");
|
|
49
|
+
tracker.forceLocationUpdate(context);
|
|
50
|
+
}
|
|
51
|
+
final Location location = tracker.getLocation(context);
|
|
45
52
|
if (location != null) {
|
|
46
53
|
setResultCode(Activity.RESULT_OK);
|
|
47
54
|
// Decimal separator is a dot
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2012-present Appium Committers
|
|
3
|
+
<p>
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
<p>
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
<p>
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
package io.appium.settings.receivers;
|
|
18
|
+
|
|
19
|
+
import android.app.Activity;
|
|
20
|
+
import android.content.BroadcastReceiver;
|
|
21
|
+
import android.content.Context;
|
|
22
|
+
import android.content.Intent;
|
|
23
|
+
import android.media.MediaScannerConnection;
|
|
24
|
+
import android.util.Log;
|
|
25
|
+
|
|
26
|
+
import java.io.File;
|
|
27
|
+
import java.util.ArrayList;
|
|
28
|
+
import java.util.Collections;
|
|
29
|
+
import java.util.List;
|
|
30
|
+
|
|
31
|
+
public class MediaScannerReceiver extends BroadcastReceiver
|
|
32
|
+
implements HasAction {
|
|
33
|
+
private static final String TAG = MediaScannerReceiver.class.getSimpleName();
|
|
34
|
+
private static final String ACTION = "io.appium.settings.scan_media";
|
|
35
|
+
private static final String PATH = "path";
|
|
36
|
+
|
|
37
|
+
private List<String> fetchFiles(File root) {
|
|
38
|
+
if (root.isFile()) {
|
|
39
|
+
return root.canRead()
|
|
40
|
+
? Collections.singletonList(root.toString())
|
|
41
|
+
: Collections.emptyList();
|
|
42
|
+
}
|
|
43
|
+
File[] items = root.listFiles();
|
|
44
|
+
if (items == null) {
|
|
45
|
+
return Collections.emptyList();
|
|
46
|
+
}
|
|
47
|
+
List<String> filePaths = new ArrayList<>();
|
|
48
|
+
for (File item : items) {
|
|
49
|
+
filePaths.addAll(fetchFiles(item));
|
|
50
|
+
}
|
|
51
|
+
return filePaths;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Responds to broadcast requests like
|
|
56
|
+
* am broadcast -a io.appium.settings.scan_media -e path /sdcard/yolo
|
|
57
|
+
* by scanning all files/folders under the given path
|
|
58
|
+
*/
|
|
59
|
+
@Override
|
|
60
|
+
public void onReceive(Context context, Intent intent) {
|
|
61
|
+
Log.d(TAG, "Scanning the requested media");
|
|
62
|
+
if (!intent.hasExtra(PATH)) {
|
|
63
|
+
Log.e(TAG, "No path has been provided");
|
|
64
|
+
setResultCode(Activity.RESULT_CANCELED);
|
|
65
|
+
setResultData("");
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
File item = new File(intent.getStringExtra(PATH));
|
|
69
|
+
if (!item.exists()) {
|
|
70
|
+
Log.e(TAG, String.format("The item at '%s' does not exist", item.toString()));
|
|
71
|
+
setResultCode(Activity.RESULT_CANCELED);
|
|
72
|
+
setResultData("");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
List<String> filePaths = fetchFiles(item);
|
|
76
|
+
if (filePaths.isEmpty()) {
|
|
77
|
+
Log.i(TAG, String.format("Found no files to scan at '%s'", item.toString()));
|
|
78
|
+
} else {
|
|
79
|
+
MediaScannerConnection.scanFile(context, filePaths.toArray(new String[0]), null, null);
|
|
80
|
+
Log.i(TAG, String.format("Successfully scanned %s file(s) at '%s'",
|
|
81
|
+
filePaths.size(), item.toString()));
|
|
82
|
+
}
|
|
83
|
+
setResultCode(Activity.RESULT_OK);
|
|
84
|
+
setResultData("");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@Override
|
|
88
|
+
public String getAction() {
|
|
89
|
+
return ACTION;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -22,9 +22,11 @@ import android.content.ComponentName;
|
|
|
22
22
|
import android.content.Context;
|
|
23
23
|
import android.content.Intent;
|
|
24
24
|
import android.provider.Settings;
|
|
25
|
-
import android.support.annotation.Nullable;
|
|
26
25
|
import android.text.TextUtils;
|
|
27
26
|
import android.util.Log;
|
|
27
|
+
|
|
28
|
+
import androidx.annotation.Nullable;
|
|
29
|
+
|
|
28
30
|
import io.appium.settings.notifications.StoredNotifications;
|
|
29
31
|
import org.json.JSONArray;
|
|
30
32
|
import org.json.JSONException;
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
package io.appium.settings.receivers;
|
|
18
18
|
|
|
19
|
+
import android.annotation.SuppressLint;
|
|
19
20
|
import android.app.Activity;
|
|
20
21
|
import android.bluetooth.BluetoothAdapter;
|
|
21
22
|
import android.bluetooth.BluetoothDevice;
|
|
@@ -27,6 +28,7 @@ import android.util.Log;
|
|
|
27
28
|
import java.lang.reflect.InvocationTargetException;
|
|
28
29
|
import java.util.Set;
|
|
29
30
|
|
|
31
|
+
@SuppressLint("MissingPermission")
|
|
30
32
|
public class UnpairBluetoothDevicesReceiver extends BroadcastReceiver implements HasAction {
|
|
31
33
|
private static final String TAG = UnpairBluetoothDevicesReceiver.class.getSimpleName();
|
|
32
34
|
|
package/build.gradle
CHANGED
|
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|
|
3
3
|
distributionPath=wrapper/dists
|
|
4
4
|
zipStoreBase=GRADLE_USER_HOME
|
|
5
5
|
zipStorePath=wrapper/dists
|
|
6
|
-
distributionUrl=https\://services.gradle.org/distributions/gradle-
|
|
6
|
+
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
## For more details on how to configure your build environment visit
|
|
2
|
+
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
|
3
|
+
#
|
|
4
|
+
# Specifies the JVM arguments used for the daemon process.
|
|
5
|
+
# The setting is particularly useful for tweaking memory settings.
|
|
6
|
+
# Default value: -Xmx1024m -XX:MaxPermSize=256m
|
|
7
|
+
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
|
8
|
+
#
|
|
9
|
+
# When configured, Gradle will run in incubating parallel mode.
|
|
10
|
+
# This option should only be used with decoupled projects. More details, visit
|
|
11
|
+
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
|
12
|
+
# org.gradle.parallel=true
|
|
13
|
+
#Fri Dec 17 09:25:55 CET 2021
|
|
14
|
+
android.useAndroidX=true
|
|
15
|
+
android.enableJetifier=true
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "io.appium.settings",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "App for dealing with Android settings",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"homepage": "https://github.com/appium/io.appium.settings",
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"appium
|
|
42
|
+
"@appium/gulp-plugins": "^6.0.0",
|
|
43
43
|
"gulp": "^4.0.0"
|
|
44
44
|
}
|
|
45
45
|
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Copyright 2012-present Appium Committers
|
|
3
|
-
<p>
|
|
4
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
you may not use this file except in compliance with the License.
|
|
6
|
-
You may obtain a copy of the License at
|
|
7
|
-
<p>
|
|
8
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
<p>
|
|
10
|
-
Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
See the License for the specific language governing permissions and
|
|
14
|
-
limitations under the License.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
package io.appium.settings.location;
|
|
18
|
-
|
|
19
|
-
import android.location.Location;
|
|
20
|
-
import android.os.Build;
|
|
21
|
-
import android.os.SystemClock;
|
|
22
|
-
|
|
23
|
-
public class LocationFactory {
|
|
24
|
-
|
|
25
|
-
private double latitude;
|
|
26
|
-
private double longitude;
|
|
27
|
-
private double altitude;
|
|
28
|
-
private float speed;
|
|
29
|
-
private boolean hasSpeed = false;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
public synchronized Location createLocation(String providerName, float accuracy) {
|
|
33
|
-
Location l = new Location(providerName);
|
|
34
|
-
l.setAccuracy(accuracy);
|
|
35
|
-
|
|
36
|
-
l.setLatitude(latitude);
|
|
37
|
-
l.setLongitude(longitude);
|
|
38
|
-
l.setAltitude(altitude);
|
|
39
|
-
if (hasSpeed) {
|
|
40
|
-
l.setSpeed(speed);
|
|
41
|
-
}
|
|
42
|
-
l.setBearing(0);
|
|
43
|
-
|
|
44
|
-
l.setTime(System.currentTimeMillis());
|
|
45
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
|
46
|
-
l.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
|
|
47
|
-
}
|
|
48
|
-
return l;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
public synchronized void setLocation(double latitude, double longitude, double altitude, float speed) {
|
|
52
|
-
this.setLocation(latitude, longitude, altitude);
|
|
53
|
-
this.speed = speed;
|
|
54
|
-
this.hasSpeed = true;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
public synchronized void setLocation(double latitude, double longitude, double altitude) {
|
|
58
|
-
this.latitude = latitude;
|
|
59
|
-
this.longitude = longitude;
|
|
60
|
-
this.altitude = altitude;
|
|
61
|
-
this.hasSpeed = false;
|
|
62
|
-
}
|
|
63
|
-
}
|