diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
index 1870783e..cd4e9dc0 100644
--- a/.github/workflows/android.yml
+++ b/.github/workflows/android.yml
@@ -14,26 +14,32 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v7
+
- name: set up JDK 17
- uses: actions/setup-java@v2
+ uses: actions/setup-java@v5
with:
java-version: '17'
- distribution: 'adopt'
+ distribution: 'temurin'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- - name: Build with Gradle
- run: ./gradlew build
- - name: Archive linter results
- uses: actions/upload-artifact@v3
- with:
- name: lint-result.html
- path: app/build/reports/lint-results-debug.html
+ - name: Assemble build artifact
+ run: ./gradlew assemble
- name: Archive debug.apk
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v7
with:
name: asteroidossync-debug.apk
path: app/build/outputs/apk/debug/app-debug.apk
+
+ - name: Run checks
+ run: ./gradlew build
+
+ - name: Archive linter results
+ uses: actions/upload-artifact@v7
+ if: always()
+ with:
+ name: lint-result.html
+ path: app/build/reports/lint-results-debug.html
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 6d8e6703..d914352b 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,15 +1,14 @@
plugins {
- id("com.android.application")
+ alias(libs.plugins.android.application)
}
android {
- compileSdk = 33
- buildToolsVersion = "30.0.3"
+ compileSdk = 34
defaultConfig {
applicationId = "org.asteroidos.sync"
minSdk = 24
- targetSdk = 33
+ targetSdk = 34
versionCode = 29
versionName = "0.29"
}
@@ -25,6 +24,7 @@ android {
srcDir("src/main/lib/android-ripple-background/library/src/main/java/")
srcDir("src/main/lib/material-intro-screen/material-intro-screen/src/main/java/")
srcDir("src/main/lib/powerampapi/poweramp_api_lib/src/")
+ srcDir("src/main/lib/easyweather/src/main/java/")
}
res {
srcDir("src/main/lib/android-ripple-background/library/src/main/res/")
@@ -45,22 +45,19 @@ android {
namespace = "org.asteroidos.sync"
}
-repositories {
- mavenCentral()
- maven("https://maven.google.com")
- maven("https://jitpack.io")
-}
-
dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
- testImplementation("junit:junit:4.13.2")
- implementation("androidx.appcompat:appcompat:1.6.1")
- implementation("androidx.legacy:legacy-support-v4:1.0.0")
- implementation("androidx.cardview:cardview:1.0.0")
- implementation("com.google.android.material:material:1.9.0")
- implementation("com.github.MagneFire:EasyWeather:1.3")
- implementation("com.google.code.gson:gson:2.10.1")
- implementation("org.osmdroid:osmdroid-android:6.1.16")
- implementation("no.nordicsemi.android.support.v18:scanner:1.6.0")
- implementation("no.nordicsemi.android:ble:2.7.2")
+ testImplementation(libs.junit)
+ implementation(libs.androidx.appcompat)
+ implementation(libs.androidx.cardview)
+ implementation(libs.material)
+ // EasyWeather is vendored under src/main/lib/easyweather (see its README);
+ // these are the runtime dependencies it needs, from Maven Central.
+ implementation(libs.retrofit)
+ implementation(libs.retrofit.converter.gson)
+ implementation(libs.okhttp.logging.interceptor)
+ implementation(libs.gson)
+ implementation(libs.osmdroid.android)
+ implementation(libs.nordic.scanner)
+ implementation(libs.nordic.ble)
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 026a17b3..9a643be6 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -11,7 +11,6 @@
-
@@ -19,6 +18,7 @@
+
@@ -87,6 +87,7 @@
android:name=".services.SynchronizationService"
android:enabled="true"
android:exported="false"
+ android:foregroundServiceType="connectedDevice"
android:label="SynchronizationService" />
mLocationEnableActivityLauncher;
+ ActivityResultLauncher mBtEnableLauncher;
LocationManager mLocationManager;
/* Synchronization service events handling */
private final ServiceConnection mConnection = new ServiceConnection() {
@@ -116,7 +119,7 @@ public void onScanResult(int callbackType, @NonNull ScanResult result) {
mListFragment.deviceDiscovered(result.getDevice());
if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- ActivityCompat.requestPermissions(getParent(), new String[]{Manifest.permission.BLUETOOTH_CONNECT}, 225);
+ ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.BLUETOOTH_CONNECT}, 225);
}
return;
}
@@ -128,6 +131,7 @@ public void onScanResult(int callbackType, @NonNull ScanResult result) {
private Fragment mPreviousFragment;
private BluetoothLeScannerCompat mScanner;
private SharedPreferences mPrefs;
+ private OnBackPressedCallback mBackCallback;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -153,6 +157,17 @@ protected void onCreate(Bundle savedInstanceState) {
new ActivityResultContracts.StartActivityForResult(),
result -> btEnableAndScan()
);
+ mBtEnableLauncher = registerForActivityResult(
+ new ActivityResultContracts.StartActivityForResult(),
+ result -> btEnableAndScan()
+ );
+ mBackCallback = new OnBackPressedCallback(true) {
+ @Override
+ public void handleOnBackPressed() {
+ handleBack();
+ }
+ };
+ getOnBackPressedDispatcher().addCallback(this, mBackCallback);
btEnable();
/* Start and/or attach to the Synchronization Service */
@@ -271,13 +286,12 @@ public void onDisconnectRequested() {
@Override
public boolean onOptionsItemSelected(MenuItem menuItem) {
if (menuItem.getItemId() == android.R.id.home)
- onBackPressed();
+ getOnBackPressedDispatcher().onBackPressed();
return (super.onOptionsItemSelected(menuItem));
}
- @Override
- public void onBackPressed() {
+ private void handleBack() {
FragmentManager fm = getSupportFragmentManager();
if (fm.getBackStackEntryCount() > 0) {
fm.popBackStack();
@@ -285,15 +299,19 @@ public void onBackPressed() {
ActionBar ab = getSupportActionBar();
if (ab != null)
ab.setDisplayHomeAsUpEnabled(false);
- } else
- finish();
- try {
- mDetailFragment = (DeviceDetailFragment) mPreviousFragment;
- } catch (ClassCastException ignored1) {
try {
- mListFragment = (DeviceListFragment) mPreviousFragment;
- } catch (ClassCastException ignored2) {
+ mDetailFragment = (DeviceDetailFragment) mPreviousFragment;
+ } catch (ClassCastException ignored1) {
+ try {
+ mListFragment = (DeviceListFragment) mPreviousFragment;
+ } catch (ClassCastException ignored2) {
+ }
}
+ } else {
+ // Nothing left on the back stack: hand control back to the platform
+ // default (finishing the activity) via the dispatcher.
+ mBackCallback.setEnabled(false);
+ getOnBackPressedDispatcher().onBackPressed();
}
}
@@ -378,16 +396,19 @@ public void onScanRequested() {
}
private void btEnable() {
- BluetoothAdapter mBtAdapter;
- mBtAdapter = BluetoothAdapter.getDefaultAdapter();
- if (!mBtAdapter.isEnabled()) {
- if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- ActivityCompat.requestPermissions(getParent(), new String[]{Manifest.permission.BLUETOOTH_CONNECT}, 225);
- }
+ BluetoothManager btManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
+ BluetoothAdapter btAdapter = btManager != null ? btManager.getAdapter() : null;
+ if (btAdapter == null) return; // device has no Bluetooth
+ if (!btAdapter.isEnabled()) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
+ && ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.BLUETOOTH_CONNECT}, 225);
return;
}
- mBtAdapter.enable();
+ // BluetoothAdapter.enable() is deprecated and a no-op since Android
+ // 13 (apps can no longer silently toggle Bluetooth), so ask the user
+ // to enable it instead.
+ mBtEnableLauncher.launch(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE));
}
}
@@ -445,9 +466,10 @@ public void onConfigurationChanged(@NonNull Configuration newConfig) {
break;
}
- finish();
- overridePendingTransition(0, 0);
- startActivity(getIntent());
+ // Recreate the activity to apply the new theme. This is the modern
+ // equivalent of the previous finish()/startActivity() restart and keeps
+ // the task and back stack intact instead of tearing them down.
+ recreate();
}
static private class SynchronizationHandler extends Handler {
diff --git a/app/src/main/java/org/asteroidos/sync/asteroid/AsteroidBleManager.java b/app/src/main/java/org/asteroidos/sync/asteroid/AsteroidBleManager.java
index 280a7938..2eee3f18 100644
--- a/app/src/main/java/org/asteroidos/sync/asteroid/AsteroidBleManager.java
+++ b/app/src/main/java/org/asteroidos/sync/asteroid/AsteroidBleManager.java
@@ -53,6 +53,14 @@ public class AsteroidBleManager extends BleManager {
// a smaller chunk size has no effect other than splitting into more writes.
private static final int MAX_ATTRIBUTE_LENGTH = 512;
+ // Request the largest ATT_MTU the BLE spec allows (517 bytes) at connection
+ // time. A bigger MTU means fewer, larger GATT writes and notifications, so
+ // syncs (notifications, screenshots, weather) transfer faster. This is only
+ // safe because CAPPED_SPLITTER above clamps every write to the 512-byte
+ // attribute limit no matter what MTU the system negotiates; the peer always
+ // falls back to a smaller MTU if it can't support the maximum.
+ private static final int GATT_MAX_MTU = 517;
+
private static final DataSplitter CAPPED_SPLITTER = (message, index, maxLength) -> {
final int size = Math.min(maxLength, MAX_ATTRIBUTE_LENGTH);
final int offset = index * size;
@@ -80,8 +88,14 @@ public AsteroidBleManager(@NonNull final Context context, SynchronizationService
}
public final void send(UUID characteristic, byte[] data) {
- writeCharacteristic(sendingCharacteristics.get(characteristic), data,
- Objects.requireNonNull(sendingCharacteristics.get(characteristic)).getWriteType()).split(CAPPED_SPLITTER).enqueue();
+ if (sendingCharacteristics == null)
+ return;
+ BluetoothGattCharacteristic gattCharacteristic = sendingCharacteristics.get(characteristic);
+ if (gattCharacteristic == null) {
+ Log.w(TAG, "No writable characteristic for " + characteristic + "; dropping write");
+ return;
+ }
+ writeCharacteristic(gattCharacteristic, data, gattCharacteristic.getWriteType()).split(CAPPED_SPLITTER).enqueue();
}
@NonNull
@@ -138,6 +152,13 @@ public final boolean isRequiredServiceSupported(@NonNull final BluetoothGatt gat
for (IConnectivityService service : mSynchronizationService.getServices().values()) {
BluetoothGattService bluetoothGattService = gatt.getService(service.getServiceUUID());
+ // A flaky reconnect can return a partial GATT table. Skipping a
+ // missing service avoids a NullPointerException here that would
+ // abort the whole connection and trap us in a retry loop.
+ if (bluetoothGattService == null) {
+ Log.w(TAG, "Service not exposed by watch, skipping: " + service.getServiceUUID());
+ continue;
+ }
List sendUuids = new ArrayList<>();
service.getCharacteristicUUIDs().forEach((uuid, direction) -> {
if (direction == IConnectivityService.Direction.TO_WATCH)
@@ -147,11 +168,13 @@ public final boolean isRequiredServiceSupported(@NonNull final BluetoothGatt gat
for (UUID uuid : sendUuids) {
BluetoothGattCharacteristic characteristic = bluetoothGattService.getCharacteristic(uuid);
- sendingCharacteristics.put(uuid, characteristic);
- bluetoothGattService.addCharacteristic(characteristic);
+ if (characteristic != null)
+ sendingCharacteristics.put(uuid, characteristic);
}
recvCallbacks.forEach((characteristic, callback) -> {
BluetoothGattCharacteristic characteristic1 = bluetoothGattService.getCharacteristic(characteristic);
+ if (characteristic1 == null)
+ return;
removeNotificationCallback(characteristic1);
setNotificationCallback(characteristic1).with((device, data) -> callback.call(data.getValue()));
enableNotifications(characteristic1).enqueue();
@@ -164,7 +187,7 @@ public final boolean isRequiredServiceSupported(@NonNull final BluetoothGatt gat
@Override
protected final void initialize() {
beginAtomicRequestQueue()
- .add(requestMtu(256) // Remember, GATT needs 3 bytes extra. This will allow packet size of 244 bytes.
+ .add(requestMtu(GATT_MAX_MTU)
.with((device, mtu) -> log(Log.INFO, "MTU set to " + mtu))
.fail((device, status) -> log(Log.WARN, "Requested MTU not supported: " + status)))
.done(device -> log(Log.INFO, "Target initialized"))
diff --git a/app/src/main/java/org/asteroidos/sync/connectivity/MediaService.java b/app/src/main/java/org/asteroidos/sync/connectivity/MediaService.java
index 676eee65..61e59489 100644
--- a/app/src/main/java/org/asteroidos/sync/connectivity/MediaService.java
+++ b/app/src/main/java/org/asteroidos/sync/connectivity/MediaService.java
@@ -76,7 +76,7 @@ public MediaService(Context ctx, IAsteroidDevice device) {
mDevice = device;
mCtx = ctx;
device.registerCallback(AsteroidUUIDS.MEDIA_COMMANDS_CHAR, (data) -> {
- if (data == null) return;
+ if (data == null || data.length < 1) return;
if (mMediaController != null) {
boolean isPoweramp = mSettings.getString(PREFS_MEDIA_CONTROLLER_PACKAGE, PREFS_MEDIA_CONTROLLER_PACKAGE_DEFAULT)
.equals(PowerampAPI.PACKAGE_NAME);
@@ -115,7 +115,7 @@ public MediaService(Context ctx, IAsteroidDevice device) {
}
break;
case MEDIA_COMMAND_VOLUME:
- if (mMediaController.getPlaybackInfo() != null) {
+ if (data.length >= 2 && mMediaController.getPlaybackInfo() != null) {
if (data[1] != mVolume) {
int delta = Math.abs(mVolume - data[1]);
int deviceDelta = 100 / mMediaController.getPlaybackInfo().getMaxVolume();
@@ -200,15 +200,16 @@ private void sendVolume(int volume) {
mDevice.send(AsteroidUUIDS.MEDIA_VOLUME_CHAR, data, MediaService.this);
}
- private final ContentObserver mVolumeChangeObserver = new ContentObserver(new Handler()) {
+ private final ContentObserver mVolumeChangeObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
// The last value of volume send to the watch.
private int reportedVolume;
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
- if (mMediaController != null && mMediaController.getPlaybackInfo() != null) {
- int vol = (100 * mMediaController.getPlaybackInfo().getCurrentVolume()) / mMediaController.getPlaybackInfo().getMaxVolume();
+ MediaController.PlaybackInfo info = mMediaController != null ? mMediaController.getPlaybackInfo() : null;
+ if (info != null && info.getMaxVolume() > 0) {
+ int vol = (100 * info.getCurrentVolume()) / info.getMaxVolume();
if (reportedVolume != vol) {
reportedVolume = vol;
@@ -269,8 +270,12 @@ public void onMetadataChanged(MediaMetadata metadata) {
getTextAsBytes(metadata, MediaMetadata.METADATA_KEY_TITLE),
MediaService.this);
- mVolume = (100 * mMediaController.getPlaybackInfo().getCurrentVolume()) / mMediaController.getPlaybackInfo().getMaxVolume();
- sendVolume(mVolume);
+ MediaController controller = mMediaController;
+ MediaController.PlaybackInfo info = controller != null ? controller.getPlaybackInfo() : null;
+ if (info != null && info.getMaxVolume() > 0) {
+ mVolume = (100 * info.getCurrentVolume()) / info.getMaxVolume();
+ sendVolume(mVolume);
+ }
}
}
diff --git a/app/src/main/java/org/asteroidos/sync/connectivity/NotificationService.java b/app/src/main/java/org/asteroidos/sync/connectivity/NotificationService.java
index de8c71ab..1dd41f3f 100644
--- a/app/src/main/java/org/asteroidos/sync/connectivity/NotificationService.java
+++ b/app/src/main/java/org/asteroidos/sync/connectivity/NotificationService.java
@@ -23,6 +23,8 @@
import android.content.Intent;
import android.content.IntentFilter;
+import androidx.core.content.ContextCompat;
+
import org.asteroidos.sync.NotificationPreferences;
import org.asteroidos.sync.asteroid.IAsteroidDevice;
import org.asteroidos.sync.dataobjects.Notification;
@@ -50,10 +52,11 @@ public void sync() {
IntentFilter filter = new IntentFilter();
filter.addAction("org.asteroidos.sync.NOTIFICATION_LISTENER");
mNReceiver = new NotificationReceiver();
- mCtx.registerReceiver(mNReceiver, filter);
+ ContextCompat.registerReceiver(mCtx, mNReceiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED);
Intent i = new Intent("org.asteroidos.sync.NOTIFICATION_LISTENER_SERVICE");
i.putExtra("command", "refresh");
+ i.setPackage(mCtx.getPackageName());
mCtx.sendBroadcast(i);
}
}
diff --git a/app/src/main/java/org/asteroidos/sync/connectivity/ScreenshotService.java b/app/src/main/java/org/asteroidos/sync/connectivity/ScreenshotService.java
index 8fd6e860..7d31288b 100644
--- a/app/src/main/java/org/asteroidos/sync/connectivity/ScreenshotService.java
+++ b/app/src/main/java/org/asteroidos/sync/connectivity/ScreenshotService.java
@@ -38,6 +38,7 @@
import android.util.Log;
import androidx.core.app.NotificationCompat;
+import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import org.asteroidos.sync.R;
@@ -59,6 +60,9 @@
public class ScreenshotService implements IConnectivityService {
private static final String NOTIFICATION_CHANNEL_ID = "screenshotservice_channel_id_01";
private final int NOTIFICATION = 2726;
+ // Sanity ceiling for an announced screenshot size; a real watch screenshot
+ // is a few tens of KB. Anything larger is treated as a corrupt header.
+ private static final int MAX_SCREENSHOT_SIZE = 10 * 1024 * 1024;
private final Context mCtx;
private final IAsteroidDevice mDevice;
@@ -94,6 +98,14 @@ public ScreenshotService(Context ctx, IAsteroidDevice device) {
}
if (mFirstNotify) {
size = bytesToInt(data);
+ // Guard against a corrupt or oversized header that would
+ // otherwise throw NegativeArraySizeException / OutOfMemoryError.
+ if (size <= 0 || size > MAX_SCREENSHOT_SIZE) {
+ mFirstNotify = true;
+ mDownloading = false;
+ totalData = null;
+ return;
+ }
totalData = new byte[size];
mFirstNotify = false;
progress = 0;
@@ -111,12 +123,22 @@ public ScreenshotService(Context ctx, IAsteroidDevice device) {
mNM.notify(NOTIFICATION, notification);
}, 0, 1, TimeUnit.SECONDS);
} else {
- if (data.length + progress <= totalData.length)
- System.arraycopy(data, 0, totalData, progress, data.length);
- progress += data.length;
+ // A data chunk arrived without a preceding header; ignore it
+ // rather than crash on a null buffer.
+ if (totalData == null) return;
+
+ // Clamp to the remaining buffer so a desynced/oversized stream
+ // can never overflow the array nor push progress past size (which
+ // would leave the progress executor running forever).
+ int remaining = totalData.length - progress;
+ int toCopy = Math.min(data.length, remaining);
+ if (toCopy > 0) {
+ System.arraycopy(data, 0, totalData, progress, toCopy);
+ progress += toCopy;
+ }
- if (size == progress) {
- processUpdate.shutdown();
+ if (progress >= size) {
+ stopProcessUpdate();
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(mCtx, NOTIFICATION_CHANNEL_ID)
.setContentTitle(mCtx.getText(R.string.screenshot))
.setLocalOnly(true);
@@ -158,7 +180,7 @@ public void sync() {
mSReceiver = new ScreenshotReqReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction("org.asteroidos.sync.SCREENSHOT_REQUEST_LISTENER");
- mCtx.registerReceiver(mSReceiver, filter);
+ ContextCompat.registerReceiver(mCtx, mSReceiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED);
mDownloading = false;
}
@@ -173,6 +195,16 @@ public void unsync() {
}
mSReceiver = null;
}
+ // A disconnect mid-download must not leave the progress executor running.
+ stopProcessUpdate();
+ mDownloading = false;
+ }
+
+ private void stopProcessUpdate() {
+ if (processUpdate != null) {
+ processUpdate.shutdown();
+ processUpdate = null;
+ }
}
private static int bytesToInt(byte[] b) {
diff --git a/app/src/main/java/org/asteroidos/sync/connectivity/SilentModeService.java b/app/src/main/java/org/asteroidos/sync/connectivity/SilentModeService.java
index 590601cc..ddd9622c 100644
--- a/app/src/main/java/org/asteroidos/sync/connectivity/SilentModeService.java
+++ b/app/src/main/java/org/asteroidos/sync/connectivity/SilentModeService.java
@@ -44,8 +44,17 @@ public final void sync() {
if (notificationPref == null) {
notificationPref = prefs.getBoolean(PREF_RINGER, false);
+ int currentRinger = am.getRingerMode();
+ // If silence-on-connect is enabled and the phone is already silent,
+ // a previous session was almost certainly killed before unsync()
+ // could restore the ringer. Recording SILENT as the "original" mode
+ // would leave the user permanently muted, so fall back to NORMAL.
+ if (notificationPref && currentRinger == AudioManager.RINGER_MODE_SILENT) {
+ currentRinger = AudioManager.RINGER_MODE_NORMAL;
+ }
+
SharedPreferences.Editor editor = prefs.edit();
- editor.putInt(PREF_ORIG_RINGER, am.getRingerMode());
+ editor.putInt(PREF_ORIG_RINGER, currentRinger);
editor.apply();
if (notificationPref) {
diff --git a/app/src/main/java/org/asteroidos/sync/connectivity/TimeService.java b/app/src/main/java/org/asteroidos/sync/connectivity/TimeService.java
index 46dbc2a7..addb23ef 100644
--- a/app/src/main/java/org/asteroidos/sync/connectivity/TimeService.java
+++ b/app/src/main/java/org/asteroidos/sync/connectivity/TimeService.java
@@ -28,6 +28,8 @@
import android.os.Handler;
import android.os.SystemClock;
+import androidx.core.content.ContextCompat;
+
import org.asteroidos.sync.asteroid.IAsteroidDevice;
import org.asteroidos.sync.utils.AsteroidUUIDS;
@@ -71,10 +73,11 @@ public final void sync() {
filter.addAction(TIME_SYNC_INTENT);
filter.addAction(Intent.ACTION_TIME_CHANGED);
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
- mCtx.registerReceiver(mSReceiver, filter);
+ ContextCompat.registerReceiver(mCtx, mSReceiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED);
// register an alarm to sync the time once a day
Intent alarmIntent = new Intent(TIME_SYNC_INTENT);
+ alarmIntent.setPackage(mCtx.getPackageName());
alarmPendingIntent = PendingIntent.getBroadcast(mCtx, 0, alarmIntent, PendingIntent.FLAG_IMMUTABLE);
alarmMgr = (AlarmManager) mCtx.getSystemService(Context.ALARM_SERVICE);
alarmMgr.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
diff --git a/app/src/main/java/org/asteroidos/sync/connectivity/WeatherService.java b/app/src/main/java/org/asteroidos/sync/connectivity/WeatherService.java
index d878df66..99686a10 100644
--- a/app/src/main/java/org/asteroidos/sync/connectivity/WeatherService.java
+++ b/app/src/main/java/org/asteroidos/sync/connectivity/WeatherService.java
@@ -18,6 +18,7 @@
package org.asteroidos.sync.connectivity;
+import android.Manifest;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
@@ -25,11 +26,16 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.location.Location;
+import android.location.LocationManager;
import android.os.Handler;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.util.Log;
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
import org.asteroidos.sync.asteroid.IAsteroidDevice;
import org.asteroidos.sync.services.GPSTracker;
@@ -59,7 +65,7 @@ public class WeatherService implements IConnectivityService {
public static final String PREFS_ZOOM = "zoom";
public static final float PREFS_ZOOM_DEFAULT = (float) 7.0;
public static final String PREFS_SYNC_WEATHER = "syncWeather";
- public static final boolean PREFS_SYNC_WEATHER_DEFAULT = false;
+ public static final boolean PREFS_SYNC_WEATHER_DEFAULT = true;
public static final String WEATHER_SYNC_INTENT = "org.asteroidos.sync.WEATHER_SYNC_REQUEST_LISTENER";
private final IAsteroidDevice mDevice;
@@ -98,9 +104,10 @@ public void sync() {
IntentFilter filter = new IntentFilter();
filter.addAction(WEATHER_SYNC_INTENT);
- mCtx.registerReceiver(mSReceiver, filter);
+ ContextCompat.registerReceiver(mCtx, mSReceiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED);
// Fire update intent every 30 Minutes to update Weather
Intent alarmIntent = new Intent(WEATHER_SYNC_INTENT);
+ alarmIntent.setPackage(mCtx.getPackageName());
mAlarmPendingIntent = PendingIntent.getBroadcast(mCtx, 0, alarmIntent, PendingIntent.FLAG_IMMUTABLE);
mAlarmMgr = (AlarmManager) mCtx.getSystemService(Context.ALARM_SERVICE);
mAlarmMgr.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
@@ -121,12 +128,47 @@ public void unsync() {
if (mAlarmMgr != null) {
mAlarmMgr.cancel(mAlarmPendingIntent);
}
+ // Release the location listener; otherwise a pending single-update
+ // request outlives the disconnect.
+ if (mGPS != null) {
+ mGPS.stopUsingGPS();
+ mGPS = null;
+ }
mSReceiver = null;
}
}
+ /**
+ * Best-effort current location from the cached last-known fix of every
+ * enabled provider. The app already holds location permission for BLE
+ * scanning, so this lets weather default to where the user actually is.
+ * Returns null if permission is missing or no fix is cached.
+ */
+ @Nullable
+ public static Location getLastKnownLocation(Context ctx) {
+ if (ContextCompat.checkSelfPermission(ctx, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
+ && ContextCompat.checkSelfPermission(ctx, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED)
+ return null;
+ LocationManager lm = (LocationManager) ctx.getSystemService(Context.LOCATION_SERVICE);
+ if (lm == null)
+ return null;
+ Location best = null;
+ try {
+ for (String provider : lm.getProviders(true)) {
+ Location location = lm.getLastKnownLocation(provider);
+ if (location == null)
+ continue;
+ if (best == null || location.getTime() > best.getTime())
+ best = location;
+ }
+ } catch (SecurityException ignored) {
+ }
+ return best;
+ }
+
private void updateWeather() {
+ boolean persistLocation = true;
if (mSettings.getBoolean(PREFS_SYNC_WEATHER, PREFS_SYNC_WEATHER_DEFAULT)) {
if (mGPS == null) {
mGPS = new GPSTracker(mCtx);
@@ -138,11 +180,18 @@ private void updateWeather() {
mLongitude = (float) mGPS.getLongitude();
mGPS.gotLocation();
if(isNearNull(mLatitude) && isNearNull(mLongitude) ) {
- // We don't have a valid Location yet
- // Use the old location until we have a new one, recheck in 2 Minutes
- Handler handler = new Handler();
- handler.postDelayed(this::updateWeather, 1000 * 60 * 2);
- return;
+ // The single-update fix has not arrived yet. Seed from the
+ // last known location so weather shows immediately on a cold
+ // start; otherwise keep the old location and recheck in 2 min.
+ Location lastKnown = getLastKnownLocation(mCtx);
+ if (lastKnown != null) {
+ mLatitude = (float) lastKnown.getLatitude();
+ mLongitude = (float) lastKnown.getLongitude();
+ } else {
+ Handler handler = new Handler();
+ handler.postDelayed(this::updateWeather, 1000 * 60 * 2);
+ return;
+ }
}
}
// } else {
@@ -154,15 +203,32 @@ private void updateWeather() {
mGPS.stopUsingGPS();
mGPS = null;
}
- mLatitude = mSettings.getFloat(PREFS_LATITUDE, PREFS_LATITUDE_DEFAULT);
- mLongitude = mSettings.getFloat(PREFS_LONGITUDE, PREFS_LONGITUDE_DEFAULT);
+ if (mSettings.contains(PREFS_LATITUDE) && mSettings.contains(PREFS_LONGITUDE)) {
+ mLatitude = mSettings.getFloat(PREFS_LATITUDE, PREFS_LATITUDE_DEFAULT);
+ mLongitude = mSettings.getFloat(PREFS_LONGITUDE, PREFS_LONGITUDE_DEFAULT);
+ } else {
+ // No location picked yet: default to the device's current location
+ // instead of a hard-coded city. Don't persist it, so weather keeps
+ // following the device until the user explicitly picks a location.
+ Location location = getLastKnownLocation(mCtx);
+ if (location != null) {
+ mLatitude = (float) location.getLatitude();
+ mLongitude = (float) location.getLongitude();
+ } else {
+ mLatitude = PREFS_LATITUDE_DEFAULT;
+ mLongitude = PREFS_LONGITUDE_DEFAULT;
+ }
+ persistLocation = false;
+ }
}
updateWeather(mLatitude, mLongitude);
- SharedPreferences.Editor editor = mSettings.edit();
- editor.putFloat(WeatherService.PREFS_LATITUDE, mLatitude);
- editor.putFloat(WeatherService.PREFS_LONGITUDE, mLongitude);
- editor.apply();
+ if (persistLocation) {
+ SharedPreferences.Editor editor = mSettings.edit();
+ editor.putFloat(WeatherService.PREFS_LATITUDE, mLatitude);
+ editor.putFloat(WeatherService.PREFS_LONGITUDE, mLongitude);
+ editor.apply();
+ }
}
private boolean isNearNull(float coord) {
diff --git a/app/src/main/java/org/asteroidos/sync/fragments/DeviceDetailFragment.java b/app/src/main/java/org/asteroidos/sync/fragments/DeviceDetailFragment.java
index 981c6b5f..df7ba54e 100644
--- a/app/src/main/java/org/asteroidos/sync/fragments/DeviceDetailFragment.java
+++ b/app/src/main/java/org/asteroidos/sync/fragments/DeviceDetailFragment.java
@@ -114,6 +114,7 @@ public void onViewCreated(View view, Bundle savedInstanceState) {
Intent iremove = new Intent("org.asteroidos.sync.NOTIFICATION_LISTENER");
iremove.putExtra("event", "removed");
iremove.putExtra("id", 0xa57e401d);
+ iremove.setPackage(requireActivity().getPackageName());
requireActivity().sendBroadcast(iremove);
Intent ipost = new Intent("org.asteroidos.sync.NOTIFICATION_LISTENER");
@@ -124,11 +125,13 @@ public void onViewCreated(View view, Bundle savedInstanceState) {
ipost.putExtra("appIcon", "ios-watch-vibrating");
ipost.putExtra("summary", getString(R.string.watch_finder));
ipost.putExtra("body", getString(R.string.phone_is_searching));
+ ipost.setPackage(requireActivity().getPackageName());
requireActivity().sendBroadcast(ipost);
});
CardView screenshotCard = view.findViewById(R.id.card_view3);
- screenshotCard.setOnClickListener(view1 -> requireActivity().sendBroadcast(new Intent("org.asteroidos.sync.SCREENSHOT_REQUEST_LISTENER")));
+ screenshotCard.setOnClickListener(view1 -> requireActivity().sendBroadcast(
+ new Intent("org.asteroidos.sync.SCREENSHOT_REQUEST_LISTENER").setPackage(requireActivity().getPackageName())));
CardView notifSettCard = view.findViewById(R.id.card_view4);
notifSettCard.setOnClickListener(notifSettCardView -> mAppSettingsListener.onAppSettingsClicked());
diff --git a/app/src/main/java/org/asteroidos/sync/fragments/WeatherSettingsFragment.java b/app/src/main/java/org/asteroidos/sync/fragments/WeatherSettingsFragment.java
index 61c42cd9..2a9bdf71 100644
--- a/app/src/main/java/org/asteroidos/sync/fragments/WeatherSettingsFragment.java
+++ b/app/src/main/java/org/asteroidos/sync/fragments/WeatherSettingsFragment.java
@@ -23,6 +23,7 @@
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
+import android.location.Location;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
@@ -76,9 +77,26 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup parent, @N
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
- float latitude = mSettings.getFloat(WeatherService.PREFS_LATITUDE, WeatherService.PREFS_LATITUDE_DEFAULT);
- float longitude = mSettings.getFloat(WeatherService.PREFS_LONGITUDE, WeatherService.PREFS_LONGITUDE_DEFAULT);
float zoom = mSettings.getFloat(WeatherService.PREFS_ZOOM, WeatherService.PREFS_ZOOM_DEFAULT);
+ float latitude;
+ float longitude;
+ if (mSettings.contains(WeatherService.PREFS_LATITUDE) && mSettings.contains(WeatherService.PREFS_LONGITUDE)) {
+ latitude = mSettings.getFloat(WeatherService.PREFS_LATITUDE, WeatherService.PREFS_LATITUDE_DEFAULT);
+ longitude = mSettings.getFloat(WeatherService.PREFS_LONGITUDE, WeatherService.PREFS_LONGITUDE_DEFAULT);
+ } else {
+ // No location chosen yet: center on the device's current location
+ // rather than the default city.
+ Location location = WeatherService.getLastKnownLocation(requireContext());
+ if (location != null) {
+ latitude = (float) location.getLatitude();
+ longitude = (float) location.getLongitude();
+ if (zoom < 10.0f)
+ zoom = 10.0f;
+ } else {
+ latitude = mSettings.getFloat(WeatherService.PREFS_LATITUDE, WeatherService.PREFS_LATITUDE_DEFAULT);
+ longitude = mSettings.getFloat(WeatherService.PREFS_LONGITUDE, WeatherService.PREFS_LONGITUDE_DEFAULT);
+ }
+ }
mOwmKey = mSettings.getString(WeatherService.PREFS_OWM_API_KEY, WeatherService.PREFS_OWM_API_KEY_DEFAULT);
mMapView = view.findViewById(R.id.map);
@@ -118,7 +136,7 @@ public void onViewCreated(View view, Bundle savedInstanceState) {
editor.apply();
// Update the Weather after changing it
- getActivity().sendBroadcast(new Intent(WeatherService.WEATHER_SYNC_INTENT));
+ getActivity().sendBroadcast(new Intent(WeatherService.WEATHER_SYNC_INTENT).setPackage(getActivity().getPackageName()));
getActivity().onBackPressed();
});
@@ -193,7 +211,7 @@ private void handleLocationToggle(boolean enable) {
editor.putBoolean(WeatherService.PREFS_SYNC_WEATHER, enable);
editor.apply();
mButton.setVisibility(enable ? View.INVISIBLE : View.VISIBLE);
- requireActivity().sendBroadcast(new Intent(WeatherService.WEATHER_SYNC_INTENT));
+ requireActivity().sendBroadcast(new Intent(WeatherService.WEATHER_SYNC_INTENT).setPackage(requireActivity().getPackageName()));
}
@Override
diff --git a/app/src/main/java/org/asteroidos/sync/services/AutostartService.java b/app/src/main/java/org/asteroidos/sync/services/AutostartService.java
index 1f2aafbf..458fa739 100644
--- a/app/src/main/java/org/asteroidos/sync/services/AutostartService.java
+++ b/app/src/main/java/org/asteroidos/sync/services/AutostartService.java
@@ -22,6 +22,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.os.Build;
import org.asteroidos.sync.MainActivity;
@@ -36,7 +37,15 @@ public void onReceive(Context context, Intent intent)
if (defaultDevMacAddr.length() > 0) {
Intent mSyncServiceIntent = new Intent(context, SynchronizationService.class);
- context.startService(mSyncServiceIntent);
+ // On Android 8+ a background context (such as a boot broadcast)
+ // is not allowed to start a plain background service; doing so
+ // throws IllegalStateException and the watch silently never
+ // reconnects after a reboot. The service promotes itself to the
+ // foreground, so start it accordingly.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
+ context.startForegroundService(mSyncServiceIntent);
+ else
+ context.startService(mSyncServiceIntent);
}
}
}
diff --git a/app/src/main/java/org/asteroidos/sync/services/NLService.java b/app/src/main/java/org/asteroidos/sync/services/NLService.java
index d8626c73..3844f2f8 100644
--- a/app/src/main/java/org/asteroidos/sync/services/NLService.java
+++ b/app/src/main/java/org/asteroidos/sync/services/NLService.java
@@ -26,12 +26,13 @@
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.os.Build;
import android.os.Handler;
+import android.os.Looper;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import androidx.core.app.NotificationCompat;
+import androidx.core.content.ContextCompat;
import org.asteroidos.sync.utils.NotificationParser;
@@ -44,6 +45,9 @@ public class NLService extends NotificationListenerService {
private NLServiceReceiver nlServiceReceiver;
private Map iconFromPackage;
private volatile boolean listenerConnected = false;
+ // A refresh was requested before the listener was connected; honour it as
+ // soon as onListenerConnected() fires instead of busy-waiting.
+ private volatile boolean refreshPending = false;
@Override
public void onCreate() {
@@ -51,7 +55,7 @@ public void onCreate() {
nlServiceReceiver = new NLServiceReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction("org.asteroidos.sync.NOTIFICATION_LISTENER_SERVICE");
- registerReceiver(nlServiceReceiver, filter);
+ ContextCompat.registerReceiver(this, nlServiceReceiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED);
iconFromPackage = new Hashtable<>();
iconFromPackage.put("code.name.monkey.retromusic", "ios-musical-notes");
@@ -198,6 +202,7 @@ public void onNotificationPosted(StatusBarNotification sbn) {
i.putExtra("summary", summary);
i.putExtra("body", body);
+ i.setPackage(getPackageName());
sendBroadcast(i);
}
@@ -206,6 +211,7 @@ public void onNotificationRemoved(StatusBarNotification sbn) {
Intent i = new Intent("org.asteroidos.sync.NOTIFICATION_LISTENER");
i.putExtra("event", "removed");
i.putExtra("id", sbn.getId());
+ i.setPackage(getPackageName());
sendBroadcast(i);
}
@@ -219,27 +225,37 @@ public void onListenerDisconnected() {
@Override
public void onListenerConnected() {
listenerConnected = true;
+ if (refreshPending) {
+ refreshPending = false;
+ pushActiveNotifications();
+ }
+ }
+
+ private void pushActiveNotifications() {
+ if (!listenerConnected) return;
+ try {
+ StatusBarNotification[] notifs = getActiveNotifications();
+ for (StatusBarNotification notif : notifs)
+ onNotificationPosted(notif);
+ } catch (SecurityException e) {
+ e.printStackTrace();
+ }
}
- @SuppressWarnings("StatementWithEmptyBody")
class NLServiceReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- if (intent.getStringExtra("command").equals("refresh")) {
- Handler handler = new Handler();
- handler.postDelayed(() -> {
- while (!listenerConnected) {
- // Sleep the spin
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- Thread.onSpinWait();
- }else {
- // Will not delay here, as we can cause the entire UI to freeze
- }
- }
- StatusBarNotification[] notifs = getActiveNotifications();
- for (StatusBarNotification notif : notifs)
- onNotificationPosted(notif);
- }, 500);
+ if ("refresh".equals(intent.getStringExtra("command"))) {
+ if (listenerConnected) {
+ // Give freshly posted notifications a moment to settle, then
+ // push them from the main thread.
+ new Handler(Looper.getMainLooper())
+ .postDelayed(NLService.this::pushActiveNotifications, 500);
+ } else {
+ // Defer until the listener connects instead of busy-waiting,
+ // which previously froze the main thread (ANR).
+ refreshPending = true;
+ }
}
}
}
diff --git a/app/src/main/java/org/asteroidos/sync/services/PhoneStateReceiver.java b/app/src/main/java/org/asteroidos/sync/services/PhoneStateReceiver.java
index 8daa3e6a..138a24a2 100644
--- a/app/src/main/java/org/asteroidos/sync/services/PhoneStateReceiver.java
+++ b/app/src/main/java/org/asteroidos/sync/services/PhoneStateReceiver.java
@@ -27,7 +27,6 @@
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
-import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import org.asteroidos.sync.R;
@@ -37,57 +36,56 @@
public class PhoneStateReceiver extends BroadcastReceiver {
- TelephonyManager telephony;
public static final String PREFS_NAME = "PhoneStatePreference";
public static final String PREF_SEND_CALL_STATE = "PhoneCallNotificationForwarding";
public void onReceive(Context context, Intent intent) {
- if (Objects.equals(intent.getAction(), ACTION_PHONE_STATE_CHANGED)){
- CallStateService callStateService = new CallStateService(context);
- telephony = (TelephonyManager) context
- .getSystemService(Context.TELEPHONY_SERVICE);
- assert telephony != null;
- telephony.listen(callStateService, PhoneStateListener.LISTEN_CALL_STATE);
+ if (Objects.equals(intent.getAction(), ACTION_PHONE_STATE_CHANGED)) {
+ // Read the call state straight from the broadcast extras. The
+ // previous implementation created a new PhoneStateListener on every
+ // PHONE_STATE broadcast and registered it via telephony.listen()
+ // without ever unregistering it. Because PHONE_STATE fires several
+ // times per call (ringing/offhook/idle) and the receiver instance is
+ // discarded after onReceive(), listeners accumulated, leaking and
+ // delivering duplicate ring notifications to the watch.
+ String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
+ if (state == null) return;
+
+ CallStateService handler = new CallStateService(context);
+ if (TelephonyManager.EXTRA_STATE_RINGING.equals(state)) {
+ handler.startRinging(intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER));
+ } else if (TelephonyManager.EXTRA_STATE_IDLE.equals(state)
+ || TelephonyManager.EXTRA_STATE_OFFHOOK.equals(state)) {
+ handler.stopRinging();
+ }
}
}
- static class CallStateService extends PhoneStateListener {
+ static class CallStateService {
private final Context context;
private final SharedPreferences prefs;
CallStateService(Context con) {
- super();
context = con;
prefs = con.getSharedPreferences(PREFS_NAME, Activity.MODE_PRIVATE);
}
- @Override
- public void onCallStateChanged(int state, String incomingNumber) {
- switch (state) {
- case TelephonyManager.CALL_STATE_IDLE:
- case TelephonyManager.CALL_STATE_OFFHOOK:
- stopRinging();
- break;
- case TelephonyManager.CALL_STATE_RINGING:
- startRinging(incomingNumber);
- break;
- }
- }
-
private String getContact(String number) {
+ if (number == null) return null;
String contact = null;
ContentResolver cr = context.getContentResolver();
Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
Cursor cursor = cr.query(uri, new String[]{ContactsContract.PhoneLookup.DISPLAY_NAME}, null, null, null);
if (cursor != null) {
- if(cursor.moveToFirst()) {
- try {
+ try {
+ if (cursor.moveToFirst()) {
contact = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.PhoneLookup.DISPLAY_NAME));
- } catch (IllegalArgumentException e){
- e.printStackTrace();
}
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ } finally {
+ cursor.close();
}
- cursor.close();
}
return contact;
}
@@ -109,6 +107,7 @@ private void startRinging(String number) {
i.putExtra("body", number);
i.putExtra("vibration", "ringtone");
+ i.setPackage(context.getPackageName());
context.sendBroadcast(i);
}
}
@@ -117,6 +116,7 @@ private void stopRinging(){
Intent i = new Intent("org.asteroidos.sync.NOTIFICATION_LISTENER");
i.putExtra("event", "removed");
i.putExtra("id", 56345);
+ i.setPackage(context.getPackageName());
context.sendBroadcast(i);
}
}
diff --git a/app/src/main/java/org/asteroidos/sync/services/SynchronizationService.java b/app/src/main/java/org/asteroidos/sync/services/SynchronizationService.java
index f1894900..9e8137be 100644
--- a/app/src/main/java/org/asteroidos/sync/services/SynchronizationService.java
+++ b/app/src/main/java/org/asteroidos/sync/services/SynchronizationService.java
@@ -25,6 +25,7 @@
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -82,10 +83,20 @@ public class SynchronizationService extends Service implements IAsteroidDevice,
HashMap bleServices;
List nonBleServices;
private NotificationManager mNM;
- private ConnectionState mState = ConnectionState.STATUS_DISCONNECTED;
+ private volatile ConnectionState mState = ConnectionState.STATUS_DISCONNECTED;
private Messenger replyTo;
private SharedPreferences mPrefs;
private AsteroidBleManager mBleMngr;
+ // Set when the user (or app teardown) explicitly asked to disconnect, so we
+ // do not fight that intent by automatically reconnecting.
+ private volatile boolean mUserInitiatedDisconnect = false;
+ private final Handler mReconnectHandler = new Handler(Looper.getMainLooper());
+ private static final long RECONNECT_DELAY_MS = 3000;
+
+ private BluetoothAdapter getBluetoothAdapter() {
+ BluetoothManager bm = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
+ return bm != null ? bm.getAdapter() : null;
+ }
final void handleConnect() {
if (mBleMngr == null) {
@@ -94,14 +105,27 @@ final void handleConnect() {
}
if (mState == ConnectionState.STATUS_CONNECTED || mState == ConnectionState.STATUS_CONNECTING) return;
+ // A new connection attempt overrides any previously requested disconnect.
+ mUserInitiatedDisconnect = false;
+ mReconnectHandler.removeCallbacksAndMessages(null);
+
mPrefs = getSharedPreferences(MainActivity.PREFS_NAME, Context.MODE_PRIVATE);
String defaultDevMacAddr = mPrefs.getString(MainActivity.PREFS_DEFAULT_MAC_ADDR, "");
if (defaultDevMacAddr.equals("")) return;
- String defaultLocalName = mPrefs.getString(MainActivity.PREFS_DEFAULT_LOC_NAME, "");
- BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(defaultDevMacAddr);
+ BluetoothAdapter adapter = getBluetoothAdapter();
+ if (adapter == null) return;
+ BluetoothDevice device = adapter.getRemoteDevice(defaultDevMacAddr);
try {
- device.createBond();
+ // Only initiate bonding when the device is not already bonded. Calling
+ // createBond() on every connect races the bonding state machine against
+ // the GATT connection and is a known cause of error 133 / failed service
+ // discovery (the "I have to forget and re-pair" symptom). When already
+ // bonded, encryption is re-established automatically on connect.
+ if (device.getBondState() == BluetoothDevice.BOND_NONE)
+ device.createBond();
mBleMngr.connect(device)
+ // autoConnect lets the OS reconnect in the background when the
+ // watch comes back into range (power efficient, survives Doze).
.useAutoConnect(true)
.timeout(100 * 1000)
.retry(3, 200)
@@ -120,10 +144,27 @@ final void handleConnect() {
}
}
+ // Re-establish the connection after an unexpected link loss. Gated on the
+ // user-initiated flag and the current state so we never reconnect against the
+ // user's wishes or double-connect while the stack is already (re)connecting.
+ private void scheduleReconnect() {
+ if (mUserInitiatedDisconnect || mDevice == null) return;
+ mReconnectHandler.removeCallbacksAndMessages(null);
+ mReconnectHandler.postDelayed(() -> {
+ if (mUserInitiatedDisconnect) return;
+ if (mState == ConnectionState.STATUS_CONNECTED || mState == ConnectionState.STATUS_CONNECTING)
+ return;
+ Log.d(TAG, "Attempting to reconnect after link loss");
+ handleConnect();
+ }, RECONNECT_DELAY_MS);
+ }
+
final void handleDisconnect() {
if (mBleMngr == null) return;
if (mState == ConnectionState.STATUS_DISCONNECTED) return;
+ mUserInitiatedDisconnect = true;
+ mReconnectHandler.removeCallbacksAndMessages(null);
bleServices.values().forEach(IService::unsync);
mBleMngr.abort();
mBleMngr.disconnect().enqueue();
@@ -172,6 +213,14 @@ public final ConnectionState getConnectionState() {
@Override
public final void send(UUID characteristic, byte[] data, IConnectivityService service) {
+ // Services are driven by broadcasts, observers, alarms and network
+ // callbacks that fire independently of the BLE link. Dropping sends while
+ // disconnected avoids crashing in the BLE layer when there is no
+ // characteristic / connection to write to.
+ if (mBleMngr == null || mState != ConnectionState.STATUS_CONNECTED) {
+ Log.w(TAG, "Dropping send to " + characteristic + ": not connected");
+ return;
+ }
mBleMngr.send(characteristic, data);
Log.d(TAG, characteristic.toString() + " " + Arrays.toString(data));
}
@@ -244,6 +293,12 @@ public final void onDeviceDisconnected(@NonNull BluetoothDevice device, int reas
mState = ConnectionState.STATUS_DISCONNECTED;
updateNotification();
unsyncServices();
+ // Only a clean, locally requested disconnect should be left alone; any
+ // other reason (link loss, timeout, peer terminated) means we lost the
+ // watch unexpectedly and should try to get it back.
+ if (reason != ConnectionObserver.REASON_SUCCESS
+ && reason != ConnectionObserver.REASON_TERMINATE_LOCAL_HOST)
+ scheduleReconnect();
}
@Override
@@ -272,7 +327,9 @@ public void onCreate() {
}
if (!(defaultDevMacAddr.equals(""))) {
- mDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(defaultDevMacAddr);
+ BluetoothAdapter adapter = getBluetoothAdapter();
+ if (adapter != null)
+ mDevice = adapter.getRemoteDevice(defaultDevMacAddr);
}
if (nonBleServices.isEmpty())
@@ -310,29 +367,37 @@ else if (mState == ConnectionState.STATUS_CONNECTED)
}
}
- if (mDevice != null) {
- Intent intent = new Intent(this, MainActivity.class);
- PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
- intent, PendingIntent.FLAG_UPDATE_CURRENT + PendingIntent.FLAG_IMMUTABLE);
-
- Notification notification = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
- .setSmallIcon(R.drawable.ic_stat_name)
- .setContentTitle(getText(R.string.app_name))
- .setContentText(status)
- .setContentIntent(contentIntent)
- .setOngoing(true)
- .setPriority(Notification.PRIORITY_MIN)
- .setShowWhen(false)
- .build();
-
- mNM.notify(NOTIFICATION, notification);
- startForeground(NOTIFICATION, notification);
- }
+ // Always promote to a foreground service immediately. When the service is
+ // launched with startForegroundService() (e.g. from boot autostart on
+ // Android 8+), startForeground() must be called within a few seconds or
+ // the system kills the process with an ANR, so this must not be gated on
+ // a device being set.
+ Intent intent = new Intent(this, MainActivity.class);
+ PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
+ intent, PendingIntent.FLAG_UPDATE_CURRENT + PendingIntent.FLAG_IMMUTABLE);
+
+ Notification notification = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_stat_name)
+ .setContentTitle(getText(R.string.app_name))
+ .setContentText(status)
+ .setContentIntent(contentIntent)
+ .setOngoing(true)
+ .setPriority(Notification.PRIORITY_MIN)
+ .setShowWhen(false)
+ .build();
+
+ mNM.notify(NOTIFICATION, notification);
+ startForeground(NOTIFICATION, notification);
}
@Override
public void onDestroy() {
- mBleMngr.disconnect();
+ mUserInitiatedDisconnect = true;
+ mReconnectHandler.removeCallbacksAndMessages(null);
+ // disconnect() only queues a request; it must be enqueued to actually run,
+ // otherwise the GATT connection is leaked when the service is destroyed.
+ if (mBleMngr != null)
+ mBleMngr.disconnect().enqueue();
mNM.cancel(NOTIFICATION);
}
@@ -348,6 +413,8 @@ public void onDeviceConnecting(@NonNull BluetoothDevice device) {
}
private void handleUnSetDevice() {
+ mUserInitiatedDisconnect = true;
+ mReconnectHandler.removeCallbacksAndMessages(null);
SharedPreferences.Editor editor = mPrefs.edit();
if (mState != ConnectionState.STATUS_DISCONNECTED) {
mBleMngr.disconnect().enqueue();
diff --git a/app/src/main/lib/easyweather/README.md b/app/src/main/lib/easyweather/README.md
new file mode 100644
index 00000000..bb7de57d
--- /dev/null
+++ b/app/src/main/lib/easyweather/README.md
@@ -0,0 +1,23 @@
+# Vendored EasyWeather
+
+These sources are vendored from the EasyWeather library:
+https://github.com/MagneFire/EasyWeather (fork of
+https://github.com/code-crusher/EasyWeather).
+
+They were previously consumed as the Gradle dependency
+`com.github.MagneFire:EasyWeather:1.3` via JitPack. JitPack can no longer
+(re)build that artifact on demand — every version reports an `Error` build
+status and the published files have been evicted — so the dependency became
+unresolvable. The sources are vendored here instead, and the library's runtime
+dependencies (Retrofit, Gson, OkHttp logging interceptor) are declared directly
+in `app/build.gradle.kts` from Maven Central.
+
+Only change from upstream: `android.support.annotation.NonNull` was migrated to
+`androidx.annotation.NonNull` (this app is AndroidX). The upstream `res/` and
+`AndroidManifest.xml` were intentionally not vendored (the code references
+neither, and the library's `app_name` string would collide with the app's).
+
+## License
+
+Copyright 2016 Vatsal Bajpai. Licensed under the Apache License, Version 2.0.
+See http://www.apache.org/licenses/LICENSE-2.0
diff --git a/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/Helper/ForecastCallback.java b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/Helper/ForecastCallback.java
new file mode 100644
index 00000000..5892abc8
--- /dev/null
+++ b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/Helper/ForecastCallback.java
@@ -0,0 +1,16 @@
+package github.vatsal.easyweather.Helper;
+
+import github.vatsal.easyweather.retrofit.models.ForecastResponseModel;
+
+/**
+ * Created by
+ --Vatsal Bajpai on
+ --6/23/2016 at
+ --4:29 PM
+ */
+public abstract class ForecastCallback {
+
+ public abstract void success(ForecastResponseModel response);
+
+ public abstract void failure(String message);
+}
diff --git a/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/Helper/TempUnitConverter.java b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/Helper/TempUnitConverter.java
new file mode 100644
index 00000000..1d57a2c3
--- /dev/null
+++ b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/Helper/TempUnitConverter.java
@@ -0,0 +1,31 @@
+package github.vatsal.easyweather.Helper;
+
+/**
+ * Created by
+ * --Vatsal Bajpai on
+ * --26/06/16 at
+ * --7:13 PM in
+ */
+public class TempUnitConverter {
+
+ public static Double convertToCelsius(String kelvin) throws NumberFormatException {
+ double inKelvin;
+ try {
+ inKelvin = Double.parseDouble(kelvin);
+ } catch (NumberFormatException e) {
+ throw e;
+ }
+ return inKelvin - 273.15;
+ }
+
+ public static Double convertToFahrenheit(String kelvin) throws NumberFormatException {
+ double inKelvin;
+ try {
+ inKelvin = Double.parseDouble(kelvin);
+ } catch (NumberFormatException e) {
+ throw e;
+ }
+ return (inKelvin - 273.15) * 1.8000 + 32.00;
+ }
+
+}
diff --git a/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/Helper/WeatherCallback.java b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/Helper/WeatherCallback.java
new file mode 100644
index 00000000..0ea889cf
--- /dev/null
+++ b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/Helper/WeatherCallback.java
@@ -0,0 +1,16 @@
+package github.vatsal.easyweather.Helper;
+
+import github.vatsal.easyweather.retrofit.models.WeatherResponseModel;
+
+/**
+ * Created by
+ --Vatsal Bajpai on
+ --6/23/2016 at
+ --4:29 PM
+ */
+public abstract class WeatherCallback {
+
+ public abstract void success(WeatherResponseModel response);
+
+ public abstract void failure(String message);
+}
diff --git a/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/WeatherMap.java b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/WeatherMap.java
new file mode 100644
index 00000000..969bc309
--- /dev/null
+++ b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/WeatherMap.java
@@ -0,0 +1,195 @@
+package github.vatsal.easyweather;
+
+import android.content.Context;
+
+import github.vatsal.easyweather.Helper.ForecastCallback;
+import github.vatsal.easyweather.Helper.WeatherCallback;
+import github.vatsal.easyweather.retrofit.api.ApiClient;
+import github.vatsal.easyweather.retrofit.api.WeatherRetrofitCallback;
+import github.vatsal.easyweather.retrofit.models.ForecastResponseModel;
+import github.vatsal.easyweather.retrofit.models.WeatherResponseModel;
+import retrofit2.Call;
+
+/**
+ * Created by
+ * --Vatsal Bajpai under
+ * --AppyWare on
+ * --22/06/16 at
+ * --2:44 AM in
+ * --OpenWeatherMapDemo
+ */
+public class WeatherMap {
+
+ Context context;
+ String APP_ID;
+
+ public WeatherMap(Context context, String APP_ID) {
+ this.context = context;
+ this.APP_ID = APP_ID;
+ }
+
+ public void getCityWeather(String city, final WeatherCallback weatherCallback) {
+ final ApiClient objApi = ApiClient.getInstance();
+ try {
+ Call objCall = null;
+
+ objCall = objApi.getApi(context).getCityWeather(APP_ID, city);
+
+ if (objCall != null) {
+ objCall.enqueue(new WeatherRetrofitCallback(context) {
+
+ @Override
+ public void onFailure(Call call, Throwable t) {
+
+ weatherCallback.failure("Failed");
+ super.onFailure(call, t);
+ }
+
+ @Override
+ protected void onResponseWeatherResponse(Call call, retrofit2.Response response) {
+
+ if (!response.isSuccessful())
+ weatherCallback.failure("Failed");
+ }
+
+ @Override
+ protected void onResponseWeatherObject(Call call, WeatherResponseModel response) {
+
+ weatherCallback.success(response);
+ }
+
+ @Override
+ protected void common() {
+
+ }
+ });
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void getLocationWeather(String latitude, String longitude, final WeatherCallback weatherCallback) {
+ final ApiClient objApi = ApiClient.getInstance();
+ try {
+ Call objCall = null;
+
+ objCall = objApi.getApi(context).getLocationWeather(APP_ID, latitude, longitude);
+
+ if (objCall != null) {
+ objCall.enqueue(new WeatherRetrofitCallback(context) {
+
+ @Override
+ public void onFailure(Call call, Throwable t) {
+
+ weatherCallback.failure("Failed");
+ super.onFailure(call, t);
+ }
+
+ @Override
+ protected void onResponseWeatherResponse(Call call, retrofit2.Response response) {
+
+ if (!response.isSuccessful())
+ weatherCallback.failure("Failed");
+ }
+
+ @Override
+ protected void onResponseWeatherObject(Call call, WeatherResponseModel response) {
+
+ weatherCallback.success(response);
+ }
+
+ @Override
+ protected void common() {
+
+ }
+ });
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void getCityForecast(String city, final ForecastCallback forecastCallback) {
+ final ApiClient objApi = ApiClient.getInstance();
+ try {
+ Call objCall = null;
+
+ objCall = objApi.getApi(context).getCityForcast(APP_ID, city);
+
+ if (objCall != null) {
+ objCall.enqueue(new WeatherRetrofitCallback(context) {
+
+ @Override
+ public void onFailure(Call call, Throwable t) {
+
+ forecastCallback.failure("Failed");
+ super.onFailure(call, t);
+ }
+
+ @Override
+ protected void onResponseWeatherResponse(Call call, retrofit2.Response response) {
+
+ if (!response.isSuccessful())
+ forecastCallback.failure("Failed");
+ }
+
+ @Override
+ protected void onResponseWeatherObject(Call call, ForecastResponseModel response) {
+
+ forecastCallback.success(response);
+ }
+
+ @Override
+ protected void common() {
+
+ }
+ });
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void getLocationForecast(String latitude, String longitude, final ForecastCallback forecastCallback) {
+ final ApiClient objApi = ApiClient.getInstance();
+ try {
+ Call objCall = null;
+
+ objCall = objApi.getApi(context).getLocationForecast(APP_ID, latitude, longitude);
+
+ if (objCall != null) {
+ objCall.enqueue(new WeatherRetrofitCallback(context) {
+
+ @Override
+ public void onFailure(Call call, Throwable t) {
+
+ forecastCallback.failure("Failed");
+ super.onFailure(call, t);
+ }
+
+ @Override
+ protected void onResponseWeatherResponse(Call call, retrofit2.Response response) {
+
+ if (!response.isSuccessful())
+ forecastCallback.failure("Failed");
+ }
+
+ @Override
+ protected void onResponseWeatherObject(Call call, ForecastResponseModel response) {
+
+ forecastCallback.success(response);
+ }
+
+ @Override
+ protected void common() {
+
+ }
+ });
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/api/ApiClient.java b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/api/ApiClient.java
new file mode 100644
index 00000000..39cb9742
--- /dev/null
+++ b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/api/ApiClient.java
@@ -0,0 +1,76 @@
+package github.vatsal.easyweather.retrofit.api;
+
+
+import android.content.Context;
+import androidx.annotation.NonNull;
+
+import java.io.IOException;
+
+import okhttp3.Interceptor;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.logging.HttpLoggingInterceptor;
+import retrofit2.Retrofit;
+import retrofit2.converter.gson.GsonConverterFactory;
+
+public class ApiClient {
+
+ private static ApiClient uniqInstance;
+ private final String URL_LIVE = "https://api.openweathermap.org/data/2.5/";
+
+ private WeatherInterface weatherInterface;
+
+ public static synchronized ApiClient getInstance() {
+ if (uniqInstance == null) {
+ uniqInstance = new ApiClient();
+ }
+ return uniqInstance;
+ }
+
+ private void ApiClient(@NonNull final Context currContext) {
+ try {
+ HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
+ // set your desired log level
+ logging.setLevel(HttpLoggingInterceptor.Level.BODY);
+
+ Interceptor headerInterceptor = new Interceptor() {
+ @Override
+ public Response intercept(Interceptor.Chain chain) throws IOException {
+ Request original = chain.request();
+ Request.Builder builder = original.newBuilder();
+ builder.method(original.method(), original.body());
+
+ Request request = builder.build();
+
+ return chain.proceed(request);
+ }
+ };
+
+ OkHttpClient httpClient = new OkHttpClient.Builder()
+ .addInterceptor(headerInterceptor)
+ .addInterceptor(logging)
+ .build();
+ String API_URL = URL_LIVE;
+
+ Retrofit retrofit = new Retrofit.Builder()
+ .baseUrl(API_URL)
+ .addConverterFactory(GsonConverterFactory.create())
+ .client(httpClient)
+ .build();
+
+ weatherInterface = retrofit.create(WeatherInterface.class);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public WeatherInterface getApi(Context currContext) {
+ if (uniqInstance == null) {
+ getInstance();
+ }
+ uniqInstance.ApiClient(currContext);
+
+ return weatherInterface;
+ }
+}
diff --git a/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/api/WeatherInterface.java b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/api/WeatherInterface.java
new file mode 100644
index 00000000..a614c46e
--- /dev/null
+++ b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/api/WeatherInterface.java
@@ -0,0 +1,29 @@
+package github.vatsal.easyweather.retrofit.api;
+
+import github.vatsal.easyweather.retrofit.models.ForecastResponseModel;
+import github.vatsal.easyweather.retrofit.models.WeatherResponseModel;
+import retrofit2.Call;
+import retrofit2.http.GET;
+import retrofit2.http.Query;
+
+public interface WeatherInterface {
+
+ @GET("weather")
+ Call getCityWeather(@Query("appid") String appid,
+ @Query("q") String city);
+
+ @GET("weather")
+ Call getLocationWeather(@Query("appid") String appid,
+ @Query("lat") String latitude,
+ @Query("lon") String longitude);
+
+ @GET("forecast")
+ Call getCityForcast(@Query("appid") String appid,
+ @Query("q") String city);
+
+ @GET("forecast")
+ Call getLocationForecast(@Query("appid") String appid,
+ @Query("lat") String latitude,
+ @Query("lon") String longitude);
+
+}
diff --git a/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/api/WeatherRetrofitCallback.java b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/api/WeatherRetrofitCallback.java
new file mode 100644
index 00000000..058f275c
--- /dev/null
+++ b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/api/WeatherRetrofitCallback.java
@@ -0,0 +1,72 @@
+package github.vatsal.easyweather.retrofit.api;
+
+import android.app.Activity;
+import android.content.Context;
+
+import retrofit2.Call;
+import retrofit2.Callback;
+import retrofit2.Response;
+
+/**
+ * Created by
+ --Vatsal Bajpai on
+ --07/03/16 at
+ --4:30 PM
+ */
+public abstract class WeatherRetrofitCallback implements Callback {
+ Activity activity;
+ Context context;
+
+ public WeatherRetrofitCallback(Activity activity) {
+ this.activity = activity;
+ }
+
+ public WeatherRetrofitCallback(Context context) {
+ this.context = context;
+ }
+
+ @Override
+ public void onResponse(Call call, Response response) {
+ common();
+ onResponseWeatherResponse(call, response);
+
+ Object obj = response.body();
+ if (obj != null) {
+ S objectResponse = (S) obj;
+ onResponseWeatherObject(call, objectResponse);
+ }
+ }
+
+ @Override
+ public void onFailure(Call call, Throwable t) {
+ common();
+ //onFailureWeather(call, t);
+ }
+
+ /**
+ * Invoked for a received HTTP response.
+ *
+ * Note: An HTTP response may still indicate an application-level failure such as a 404 or 500.
+ * Call {@link Response#isSuccess()} to determine if the response indicates success.
+ */
+ protected abstract void onResponseWeatherResponse(Call call, Response response);
+
+ /**
+ * Invoked for a received HTTP response.
+ *
+ * Note: An HTTP response may still indicate an application-level failure such as a 404 or 500.
+ * Call {@link Response#isSuccess()} to determine if the response indicates success.
+ */
+ protected abstract void onResponseWeatherObject(Call call, S response);
+
+ /**
+ * Invoked when a network exception occurred talking to the server or when an unexpected
+ * exception occurred creating the request or processing the response.
+ */
+ //protected abstract void onFailureWeather(Call call, Throwable t);
+
+ /**
+ * Invoked everyTime
+ */
+ protected abstract void common();
+}
diff --git a/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/City.java b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/City.java
new file mode 100644
index 00000000..6849cf6a
--- /dev/null
+++ b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/City.java
@@ -0,0 +1,76 @@
+package github.vatsal.easyweather.retrofit.models;
+
+/**
+ * Created by
+ * --Vatsal Bajpai under
+ * --AppyWare on
+ * --23/06/16 at
+ * --1:49 AM in
+ * --OpenWeatherMapDemo
+ */
+public class City {
+ private Coord coord;
+
+ private String id;
+
+ private Sys sys;
+
+ private String name;
+
+ private String population;
+
+ private String country;
+
+ public Coord getCoord() {
+ return coord;
+ }
+
+ public void setCoord(Coord coord) {
+ this.coord = coord;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public Sys getSys() {
+ return sys;
+ }
+
+ public void setSys(Sys sys) {
+ this.sys = sys;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getPopulation() {
+ return population;
+ }
+
+ public void setPopulation(String population) {
+ this.population = population;
+ }
+
+ public String getCountry() {
+ return country;
+ }
+
+ public void setCountry(String country) {
+ this.country = country;
+ }
+
+ @Override
+ public String toString() {
+ return "ClassPojo [coord = " + coord + ", id = " + id + ", sys = " + sys + ", name = " + name + ", population = " + population + ", country = " + country + "]";
+ }
+}
diff --git a/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/Clouds.java b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/Clouds.java
new file mode 100644
index 00000000..a53df55a
--- /dev/null
+++ b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/Clouds.java
@@ -0,0 +1,26 @@
+package github.vatsal.easyweather.retrofit.models;
+
+/**
+ * Created by
+ * --Vatsal Bajpai under
+ * --AppyWare on
+ * --22/06/16 at
+ * --8:22 PM in
+ * --OpenWeatherMapDemo
+ */
+public class Clouds {
+ private String all;
+
+ public String getAll() {
+ return all;
+ }
+
+ public void setAll(String all) {
+ this.all = all;
+ }
+
+ @Override
+ public String toString() {
+ return "ClassPojo [all = " + all + "]";
+ }
+}
diff --git a/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/Coord.java b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/Coord.java
new file mode 100644
index 00000000..588740e2
--- /dev/null
+++ b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/Coord.java
@@ -0,0 +1,36 @@
+package github.vatsal.easyweather.retrofit.models;
+
+/**
+ * Created by
+ * --Vatsal Bajpai under
+ * --AppyWare on
+ * --22/06/16 at
+ * --8:23 PM in
+ * --OpenWeatherMapDemo
+ */
+public class Coord {
+ private String lon;
+
+ private String lat;
+
+ public String getLon() {
+ return lon;
+ }
+
+ public void setLon(String lon) {
+ this.lon = lon;
+ }
+
+ public String getLat() {
+ return lat;
+ }
+
+ public void setLat(String lat) {
+ this.lat = lat;
+ }
+
+ @Override
+ public String toString() {
+ return "ClassPojo [lon = " + lon + ", lat = " + lat + "]";
+ }
+}
diff --git a/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/ForecastResponseModel.java b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/ForecastResponseModel.java
new file mode 100644
index 00000000..5a4da109
--- /dev/null
+++ b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/ForecastResponseModel.java
@@ -0,0 +1,66 @@
+package github.vatsal.easyweather.retrofit.models;
+
+/**
+ * Created by
+ * --Vatsal Bajpai under
+ * --AppyWare on
+ * --23/06/16 at
+ * --1:48 AM in
+ * --OpenWeatherMapDemo
+ */
+public class ForecastResponseModel {
+ private String message;
+
+ private String cnt;
+
+ private String cod;
+
+ private List[] list;
+
+ private City city;
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public String getCnt() {
+ return cnt;
+ }
+
+ public void setCnt(String cnt) {
+ this.cnt = cnt;
+ }
+
+ public String getCod() {
+ return cod;
+ }
+
+ public void setCod(String cod) {
+ this.cod = cod;
+ }
+
+ public List[] getList() {
+ return list;
+ }
+
+ public void setList(List[] list) {
+ this.list = list;
+ }
+
+ public City getCity() {
+ return city;
+ }
+
+ public void setCity(City city) {
+ this.city = city;
+ }
+
+ @Override
+ public String toString() {
+ return "ClassPojo [message = " + message + ", cnt = " + cnt + ", cod = " + cod + ", list = " + list + ", city = " + city + "]";
+ }
+}
diff --git a/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/List.java b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/List.java
new file mode 100644
index 00000000..d0108560
--- /dev/null
+++ b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/List.java
@@ -0,0 +1,96 @@
+package github.vatsal.easyweather.retrofit.models;
+
+/**
+ * Created by
+ * --Vatsal Bajpai under
+ * --AppyWare on
+ * --23/06/16 at
+ * --1:50 AM in
+ * --OpenWeatherMapDemo
+ */
+public class List {
+ private Clouds clouds;
+
+ private String dt;
+
+ private Wind wind;
+
+ private Sys sys;
+
+ private Weather[] weather;
+
+ private String dt_txt;
+
+ private Rain rain;
+
+ private Main main;
+
+ public Clouds getClouds() {
+ return clouds;
+ }
+
+ public void setClouds(Clouds clouds) {
+ this.clouds = clouds;
+ }
+
+ public String getDt() {
+ return dt;
+ }
+
+ public void setDt(String dt) {
+ this.dt = dt;
+ }
+
+ public Wind getWind() {
+ return wind;
+ }
+
+ public void setWind(Wind wind) {
+ this.wind = wind;
+ }
+
+ public Sys getSys() {
+ return sys;
+ }
+
+ public void setSys(Sys sys) {
+ this.sys = sys;
+ }
+
+ public Weather[] getWeather() {
+ return weather;
+ }
+
+ public void setWeather(Weather[] weather) {
+ this.weather = weather;
+ }
+
+ public String getDt_txt() {
+ return dt_txt;
+ }
+
+ public void setDt_txt(String dt_txt) {
+ this.dt_txt = dt_txt;
+ }
+
+ public Rain getRain() {
+ return rain;
+ }
+
+ public void setRain(Rain rain) {
+ this.rain = rain;
+ }
+
+ public Main getMain() {
+ return main;
+ }
+
+ public void setMain(Main main) {
+ this.main = main;
+ }
+
+ @Override
+ public String toString() {
+ return "ClassPojo [clouds = " + clouds + ", dt = " + dt + ", wind = " + wind + ", sys = " + sys + ", weather = " + weather + ", dt_txt = " + dt_txt + ", rain = " + rain + ", main = " + main + "]";
+ }
+}
diff --git a/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/Main.java b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/Main.java
new file mode 100644
index 00000000..1fbe42db
--- /dev/null
+++ b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/Main.java
@@ -0,0 +1,66 @@
+package github.vatsal.easyweather.retrofit.models;
+
+/**
+ * Created by
+ * --Vatsal Bajpai under
+ * --AppyWare on
+ * --22/06/16 at
+ * --8:23 PM in
+ * --OpenWeatherMapDemo
+ */
+public class Main {
+ private String humidity;
+
+ private String pressure;
+
+ private String temp_max;
+
+ private String temp_min;
+
+ private String temp;
+
+ public String getHumidity() {
+ return humidity;
+ }
+
+ public void setHumidity(String humidity) {
+ this.humidity = humidity;
+ }
+
+ public String getPressure() {
+ return pressure;
+ }
+
+ public void setPressure(String pressure) {
+ this.pressure = pressure;
+ }
+
+ public String getTemp_max() {
+ return temp_max;
+ }
+
+ public void setTemp_max(String temp_max) {
+ this.temp_max = temp_max;
+ }
+
+ public String getTemp_min() {
+ return temp_min;
+ }
+
+ public void setTemp_min(String temp_min) {
+ this.temp_min = temp_min;
+ }
+
+ public String getTemp() {
+ return temp;
+ }
+
+ public void setTemp(String temp) {
+ this.temp = temp;
+ }
+
+ @Override
+ public String toString() {
+ return "ClassPojo [humidity = " + humidity + ", pressure = " + pressure + ", temp_max = " + temp_max + ", temp_min = " + temp_min + ", temp = " + temp + "]";
+ }
+}
diff --git a/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/Rain.java b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/Rain.java
new file mode 100644
index 00000000..2aa9b62b
--- /dev/null
+++ b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/Rain.java
@@ -0,0 +1,29 @@
+package github.vatsal.easyweather.retrofit.models;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Created by
+ * --Vatsal Bajpai under
+ * --AppyWare on
+ * --23/06/16 at
+ * --1:50 AM in
+ * --OpenWeatherMapDemo
+ */
+
+public class Rain {
+
+ @SerializedName("3h")
+ @Expose
+ private Double _3h;
+
+ public Double get3h() {
+ return _3h;
+ }
+
+ public void set3h(Double _3h) {
+ this._3h = _3h;
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/Sys.java b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/Sys.java
new file mode 100644
index 00000000..50b5bcba
--- /dev/null
+++ b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/Sys.java
@@ -0,0 +1,76 @@
+package github.vatsal.easyweather.retrofit.models;
+
+/**
+ * Created by
+ * --Vatsal Bajpai under
+ * --AppyWare on
+ * --22/06/16 at
+ * --8:21 PM in
+ * --OpenWeatherMapDemo
+ */
+public class Sys {
+ private String message;
+
+ private String id;
+
+ private String sunset;
+
+ private String sunrise;
+
+ private String type;
+
+ private String country;
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getSunset() {
+ return sunset;
+ }
+
+ public void setSunset(String sunset) {
+ this.sunset = sunset;
+ }
+
+ public String getSunrise() {
+ return sunrise;
+ }
+
+ public void setSunrise(String sunrise) {
+ this.sunrise = sunrise;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getCountry() {
+ return country;
+ }
+
+ public void setCountry(String country) {
+ this.country = country;
+ }
+
+ @Override
+ public String toString() {
+ return "ClassPojo [message = " + message + ", id = " + id + ", sunset = " + sunset + ", sunrise = " + sunrise + ", type = " + type + ", country = " + country + "]";
+ }
+}
diff --git a/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/Weather.java b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/Weather.java
new file mode 100644
index 00000000..ce086277
--- /dev/null
+++ b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/Weather.java
@@ -0,0 +1,60 @@
+package github.vatsal.easyweather.retrofit.models;
+
+/**
+ * Created by
+ * --Vatsal Bajpai under
+ * --AppyWare on
+ * --22/06/16 at
+ * --8:23 PM in
+ * --OpenWeatherMapDemo
+ */
+public class Weather {
+ private String id;
+
+ private String icon;
+
+ private String description;
+
+ private String main;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getIcon() {
+ return icon;
+ }
+
+ public String getIconLink() {
+ return "https://openweathermap.org/img/w/" + icon + ".png";
+ }
+
+ public void setIcon(String icon) {
+ this.icon = icon;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getMain() {
+ return main;
+ }
+
+ public void setMain(String main) {
+ this.main = main;
+ }
+
+ @Override
+ public String toString() {
+ return "ClassPojo [id = " + id + ", icon = " + icon + ", description = " + description + ", main = " + main + "]";
+ }
+}
diff --git a/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/WeatherResponseModel.java b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/WeatherResponseModel.java
new file mode 100644
index 00000000..86bca550
--- /dev/null
+++ b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/WeatherResponseModel.java
@@ -0,0 +1,139 @@
+package github.vatsal.easyweather.retrofit.models;
+
+import java.io.Serializable;
+
+/**
+ * Created by
+ * --Vatsal Bajpai under
+ * --AppyWare on
+ * --17/06/16 at
+ * --3:16 PM in
+ * --PopularMoviesApp
+ */
+public class WeatherResponseModel implements Serializable {
+
+ private String id;
+
+ private String dt;
+
+ private Clouds clouds;
+
+ private Coord coord;
+
+ private Wind wind;
+
+ private String cod;
+
+ private Sys sys;
+
+ private String name;
+
+ private String base;
+
+ private Weather[] weather;
+
+ private String rain;
+
+ private Main main;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getDt() {
+ return dt;
+ }
+
+ public void setDt(String dt) {
+ this.dt = dt;
+ }
+
+ public Clouds getClouds() {
+ return clouds;
+ }
+
+ public void setClouds(Clouds clouds) {
+ this.clouds = clouds;
+ }
+
+ public Coord getCoord() {
+ return coord;
+ }
+
+ public void setCoord(Coord coord) {
+ this.coord = coord;
+ }
+
+ public Wind getWind() {
+ return wind;
+ }
+
+ public void setWind(Wind wind) {
+ this.wind = wind;
+ }
+
+ public String getCod() {
+ return cod;
+ }
+
+ public void setCod(String cod) {
+ this.cod = cod;
+ }
+
+ public Sys getSys() {
+ return sys;
+ }
+
+ public void setSys(Sys sys) {
+ this.sys = sys;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getBase() {
+ return base;
+ }
+
+ public void setBase(String base) {
+ this.base = base;
+ }
+
+ public Weather[] getWeather() {
+ return weather;
+ }
+
+ public void setWeather(Weather[] weather) {
+ this.weather = weather;
+ }
+
+ public String getRain() {
+ return rain;
+ }
+
+ public void setRain(String rain) {
+ this.rain = rain;
+ }
+
+ public Main getMain() {
+ return main;
+ }
+
+ public void setMain(Main main) {
+ this.main = main;
+ }
+
+ @Override
+ public String toString() {
+ return "ClassPojo [id = " + id + ", dt = " + dt + ", clouds = " + clouds + ", coord = " + coord + ", wind = " + wind + ", cod = " + cod + ", sys = " + sys + ", name = " + name + ", base = " + base + ", weather = " + weather + ", rain = " + rain + ", main = " + main + "]";
+ }
+}
diff --git a/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/Wind.java b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/Wind.java
new file mode 100644
index 00000000..715af6b0
--- /dev/null
+++ b/app/src/main/lib/easyweather/src/main/java/github/vatsal/easyweather/retrofit/models/Wind.java
@@ -0,0 +1,36 @@
+package github.vatsal.easyweather.retrofit.models;
+
+/**
+ * Created by
+ * --Vatsal Bajpai under
+ * --AppyWare on
+ * --22/06/16 at
+ * --8:22 PM in
+ * --OpenWeatherMapDemo
+ */
+public class Wind {
+ private String speed;
+
+ private String deg;
+
+ public String getSpeed() {
+ return speed;
+ }
+
+ public void setSpeed(String speed) {
+ this.speed = speed;
+ }
+
+ public String getDeg() {
+ return deg;
+ }
+
+ public void setDeg(String deg) {
+ this.deg = deg;
+ }
+
+ @Override
+ public String toString() {
+ return "ClassPojo [speed = " + speed + ", deg = " + deg + "]";
+ }
+}
diff --git a/app/src/main/lib/sweetblue/.gitignore b/app/src/main/lib/sweetblue/.gitignore
deleted file mode 100644
index d0c61a3d..00000000
--- a/app/src/main/lib/sweetblue/.gitignore
+++ /dev/null
@@ -1,24 +0,0 @@
-!**/bin
-**/bin/*
-!**/bin/*.apk
-.svn
-**.DS_Store
-.gradle/
-build/
-scripts/build
-scripts/script_output/
-**/proguard/
-**/doc
-**/docs
-**/scripts/script_output
-samples/hello_ble/.settings/org.eclipse.jdt.core.prefs
-samples/ble_util/.settings/org.eclipse.jdt.core.prefs
-*.iml
-.idea/
-library/build
-library/script_output/
-local.properties
-library/app.iml
-sweetblue.iml
-library/sweetblue-app.iml
-captures/
\ No newline at end of file
diff --git a/app/src/main/lib/sweetblue/.travis.yml b/app/src/main/lib/sweetblue/.travis.yml
deleted file mode 100644
index 7ae6801b..00000000
--- a/app/src/main/lib/sweetblue/.travis.yml
+++ /dev/null
@@ -1,23 +0,0 @@
-language: android
-before_script: ./prebuild.sh
-android:
- components:
- - tools
- - tools
- - android-26
- - doc
- - platform-tools
-script:
- - ./gradlew tester:clean tester:testReleaseUnitTest fullBuild
-cache:
- directories:
- - $HOME/.gradle/caches/
- - $HOME/.gradle/wrapper/
-before_install:
- - echo y | android update sdk --no-ui --all --filter build-tools-26.0.2
-jdk:
-- oraclejdk8
-env:
- global:
- - secure: pSUSE9dtc3zPXivupb+TuwfjBBfig/tZP8EW8HOErM/Jc8b2Dr8e0+daXEMuHVPyqOM8TVwWhdWViLgznhz0N9oO8tWBSu7bweJOyZCczulpKGbeheLgCTJ2zJib6rW5dPZ7pkzb5AViKFKVU0lhZqDHtQN6ja6dddFXPkEJMraKduVfTvFPMH5n3te1yINiZX5KaMann09DmDLGHVSANtdX2U0iLVghpZoS09kOTsShUhk4YxOj2BuKzs6wigruursSsVp1+E/SbsJ0FBQiYAahzZgkK1VmUexgn0EyAX42DJMxQKw576mxxtNtFDy4gGRCUq77+CmydHZM/j3zi6cu7tOFSm5r5+ayzy1w/vBa8RE+vBs4S3aH0LICdum/+W1t+ZLjXd8PFTU7O2H5xUeF05DLBYBmke9B9ePBxo4Lj/54wuLXuJDbGPk3w2GWqdM84ltQy+6b5zHDArgC7ByvIV+0K1He+S1roqO7ALLrS9Ceu0vcqJE4ii9gAkUG7OytVJREU+QdACCgSVAU4P8eVMcNuf1roaycDEsGs3GBaBnk9TDkW/cCgD3m/YzKay4YVn4YYWUAYu4VCtJUtCez7HY4Ini9CTyVnhA0TUV0hyf+Ha8CLr2k22BoEjX6yiOwDQNZEKxxnQcd/VG9uP7+Obt83fpAnoWWIubJ2+8=
- - secure: kFfBXa63iOlSmi1i/WeOJhr6/ON1VRSM4e7rxw5Fmk6LaB5snc9kZpxsaugPPFKxqZEu8WJ6wHZznlPUWGQt+58c3yeRuGMqbaCMWiU731m9OHMvdc20SLFZKWLdv16XUXlVSb//0eT3MHgrF9u031QBWIfpIDVMoLIeg1WFj2KxLTun9W9lPEgOOXacI7xAVSmbCZCh07Szus0yT80Ivs5xU0CGYkmff6S+IORTCD2YKfTB4eFPKgFhkRtvVIYl3UMnnzBhlf/6+xShwIeyy7Em4Sh/x2ig6wFHo5nXZ7chCnM3oJ4qDbIXtkdpmIeA6Ya1HVeV+INDyEGinrd48brCAODjMXhj7olI9bmcNwSUgyu7ZEz4zEFbBaN1XY2uK0RBWCDjPsQZ1o/ERoRr0Ao5IiK5nVB8wLZ5/fG5dMjV8cfsqx/ix4qI8i3/AVg9IjazTbUvodv8o8gOGblSMIj8ddzHFvYWEL3iIV9hxJmSC2C2B7hnHF01ALjzt9cuiF9+o0NQTzFjBuqui7OLQrePimVGioX5NJ3/p9B3+QvassQ16TUH3au55OTtI0Vqt75lwwB376oIA4+Yh/lzByKay0+Py3fBAZn0ccs4B3YAJpyiA6kr4y0BkZ9fHD/SD8Zdb+eLGD0rQb0dsSQOdSU8QxJwM0oXwfFQSvm2yb8=
diff --git a/app/src/main/lib/sweetblue/LICENSE b/app/src/main/lib/sweetblue/LICENSE
deleted file mode 100644
index 6b156fe1..00000000
--- a/app/src/main/lib/sweetblue/LICENSE
+++ /dev/null
@@ -1,675 +0,0 @@
-GNU GENERAL PUBLIC LICENSE
- Version 3, 29 June 2007
-
- Copyright (C) 2007 Free Software Foundation, Inc.
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
- Preamble
-
- The GNU General Public License is a free, copyleft license for
-software and other kinds of works.
-
- The licenses for most software and other practical works are designed
-to take away your freedom to share and change the works. By contrast,
-the GNU General Public License is intended to guarantee your freedom to
-share and change all versions of a program--to make sure it remains free
-software for all its users. We, the Free Software Foundation, use the
-GNU General Public License for most of our software; it applies also to
-any other work released this way by its authors. You can apply it to
-your programs, too.
-
- When we speak of free software, we are referring to freedom, not
-price. Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-them if you wish), that you receive source code or can get it if you
-want it, that you can change the software or use pieces of it in new
-free programs, and that you know you can do these things.
-
- To protect your rights, we need to prevent others from denying you
-these rights or asking you to surrender the rights. Therefore, you have
-certain responsibilities if you distribute copies of the software, or if
-you modify it: responsibilities to respect the freedom of others.
-
- For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must pass on to the recipients the same
-freedoms that you received. You must make sure that they, too, receive
-or can get the source code. And you must show them these terms so they
-know their rights.
-
- Developers that use the GNU GPL protect your rights with two steps:
-(1) assert copyright on the software, and (2) offer you this License
-giving you legal permission to copy, distribute and/or modify it.
-
- For the developers' and authors' protection, the GPL clearly explains
-that there is no warranty for this free software. For both users' and
-authors' sake, the GPL requires that modified versions be marked as
-changed, so that their problems will not be attributed erroneously to
-authors of previous versions.
-
- Some devices are designed to deny users access to install or run
-modified versions of the software inside them, although the manufacturer
-can do so. This is fundamentally incompatible with the aim of
-protecting users' freedom to change the software. The systematic
-pattern of such abuse occurs in the area of products for individuals to
-use, which is precisely where it is most unacceptable. Therefore, we
-have designed this version of the GPL to prohibit the practice for those
-products. If such problems arise substantially in other domains, we
-stand ready to extend this provision to those domains in future versions
-of the GPL, as needed to protect the freedom of users.
-
- Finally, every program is threatened constantly by software patents.
-States should not allow patents to restrict development and use of
-software on general-purpose computers, but in those that do, we wish to
-avoid the special danger that patents applied to a free program could
-make it effectively proprietary. To prevent this, the GPL assures that
-patents cannot be used to render the program non-free.
-
- The precise terms and conditions for copying, distribution and
-modification follow.
-
- TERMS AND CONDITIONS
-
- 0. Definitions.
-
- "This License" refers to version 3 of the GNU General Public License.
-
- "Copyright" also means copyright-like laws that apply to other kinds of
-works, such as semiconductor masks.
-
- "The Program" refers to any copyrightable work licensed under this
-License. Each licensee is addressed as "you". "Licensees" and
-"recipients" may be individuals or organizations.
-
- To "modify" a work means to copy from or adapt all or part of the work
-in a fashion requiring copyright permission, other than the making of an
-exact copy. The resulting work is called a "modified version" of the
-earlier work or a work "based on" the earlier work.
-
- A "covered work" means either the unmodified Program or a work based
-on the Program.
-
- To "propagate" a work means to do anything with it that, without
-permission, would make you directly or secondarily liable for
-infringement under applicable copyright law, except executing it on a
-computer or modifying a private copy. Propagation includes copying,
-distribution (with or without modification), making available to the
-public, and in some countries other activities as well.
-
- To "convey" a work means any kind of propagation that enables other
-parties to make or receive copies. Mere interaction with a user through
-a computer network, with no transfer of a copy, is not conveying.
-
- An interactive user interface displays "Appropriate Legal Notices"
-to the extent that it includes a convenient and prominently visible
-feature that (1) displays an appropriate copyright notice, and (2)
-tells the user that there is no warranty for the work (except to the
-extent that warranties are provided), that licensees may convey the
-work under this License, and how to view a copy of this License. If
-the interface presents a list of user commands or options, such as a
-menu, a prominent item in the list meets this criterion.
-
- 1. Source Code.
-
- The "source code" for a work means the preferred form of the work
-for making modifications to it. "Object code" means any non-source
-form of a work.
-
- A "Standard Interface" means an interface that either is an official
-standard defined by a recognized standards body, or, in the case of
-interfaces specified for a particular programming language, one that
-is widely used among developers working in that language.
-
- The "System Libraries" of an executable work include anything, other
-than the work as a whole, that (a) is included in the normal form of
-packaging a Major Component, but which is not part of that Major
-Component, and (b) serves only to enable use of the work with that
-Major Component, or to implement a Standard Interface for which an
-implementation is available to the public in source code form. A
-"Major Component", in this context, means a major essential component
-(kernel, window system, and so on) of the specific operating system
-(if any) on which the executable work runs, or a compiler used to
-produce the work, or an object code interpreter used to run it.
-
- The "Corresponding Source" for a work in object code form means all
-the source code needed to generate, install, and (for an executable
-work) run the object code and to modify the work, including scripts to
-control those activities. However, it does not include the work's
-System Libraries, or general-purpose tools or generally available free
-programs which are used unmodified in performing those activities but
-which are not part of the work. For example, Corresponding Source
-includes interface definition files associated with source files for
-the work, and the source code for shared libraries and dynamically
-linked subprograms that the work is specifically designed to require,
-such as by intimate data communication or control flow between those
-subprograms and other parts of the work.
-
- The Corresponding Source need not include anything that users
-can regenerate automatically from other parts of the Corresponding
-Source.
-
- The Corresponding Source for a work in source code form is that
-same work.
-
- 2. Basic Permissions.
-
- All rights granted under this License are granted for the term of
-copyright on the Program, and are irrevocable provided the stated
-conditions are met. This License explicitly affirms your unlimited
-permission to run the unmodified Program. The output from running a
-covered work is covered by this License only if the output, given its
-content, constitutes a covered work. This License acknowledges your
-rights of fair use or other equivalent, as provided by copyright law.
-
- You may make, run and propagate covered works that you do not
-convey, without conditions so long as your license otherwise remains
-in force. You may convey covered works to others for the sole purpose
-of having them make modifications exclusively for you, or provide you
-with facilities for running those works, provided that you comply with
-the terms of this License in conveying all material for which you do
-not control copyright. Those thus making or running the covered works
-for you must do so exclusively on your behalf, under your direction
-and control, on terms that prohibit them from making any copies of
-your copyrighted material outside their relationship with you.
-
- Conveying under any other circumstances is permitted solely under
-the conditions stated below. Sublicensing is not allowed; section 10
-makes it unnecessary.
-
- 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
-
- No covered work shall be deemed part of an effective technological
-measure under any applicable law fulfilling obligations under article
-11 of the WIPO copyright treaty adopted on 20 December 1996, or
-similar laws prohibiting or restricting circumvention of such
-measures.
-
- When you convey a covered work, you waive any legal power to forbid
-circumvention of technological measures to the extent such circumvention
-is effected by exercising rights under this License with respect to
-the covered work, and you disclaim any intention to limit operation or
-modification of the work as a means of enforcing, against the work's
-users, your or third parties' legal rights to forbid circumvention of
-technological measures.
-
- 4. Conveying Verbatim Copies.
-
- You may convey verbatim copies of the Program's source code as you
-receive it, in any medium, provided that you conspicuously and
-appropriately publish on each copy an appropriate copyright notice;
-keep intact all notices stating that this License and any
-non-permissive terms added in accord with section 7 apply to the code;
-keep intact all notices of the absence of any warranty; and give all
-recipients a copy of this License along with the Program.
-
- You may charge any price or no price for each copy that you convey,
-and you may offer support or warranty protection for a fee.
-
- 5. Conveying Modified Source Versions.
-
- You may convey a work based on the Program, or the modifications to
-produce it from the Program, in the form of source code under the
-terms of section 4, provided that you also meet all of these conditions:
-
- a) The work must carry prominent notices stating that you modified
- it, and giving a relevant date.
-
- b) The work must carry prominent notices stating that it is
- released under this License and any conditions added under section
- 7. This requirement modifies the requirement in section 4 to
- "keep intact all notices".
-
- c) You must license the entire work, as a whole, under this
- License to anyone who comes into possession of a copy. This
- License will therefore apply, along with any applicable section 7
- additional terms, to the whole of the work, and all its parts,
- regardless of how they are packaged. This License gives no
- permission to license the work in any other way, but it does not
- invalidate such permission if you have separately received it.
-
- d) If the work has interactive user interfaces, each must display
- Appropriate Legal Notices; however, if the Program has interactive
- interfaces that do not display Appropriate Legal Notices, your
- work need not make them do so.
-
- A compilation of a covered work with other separate and independent
-works, which are not by their nature extensions of the covered work,
-and which are not combined with it such as to form a larger program,
-in or on a volume of a storage or distribution medium, is called an
-"aggregate" if the compilation and its resulting copyright are not
-used to limit the access or legal rights of the compilation's users
-beyond what the individual works permit. Inclusion of a covered work
-in an aggregate does not cause this License to apply to the other
-parts of the aggregate.
-
- 6. Conveying Non-Source Forms.
-
- You may convey a covered work in object code form under the terms
-of sections 4 and 5, provided that you also convey the
-machine-readable Corresponding Source under the terms of this License,
-in one of these ways:
-
- a) Convey the object code in, or embodied in, a physical product
- (including a physical distribution medium), accompanied by the
- Corresponding Source fixed on a durable physical medium
- customarily used for software interchange.
-
- b) Convey the object code in, or embodied in, a physical product
- (including a physical distribution medium), accompanied by a
- written offer, valid for at least three years and valid for as
- long as you offer spare parts or customer support for that product
- model, to give anyone who possesses the object code either (1) a
- copy of the Corresponding Source for all the software in the
- product that is covered by this License, on a durable physical
- medium customarily used for software interchange, for a price no
- more than your reasonable cost of physically performing this
- conveying of source, or (2) access to copy the
- Corresponding Source from a network server at no charge.
-
- c) Convey individual copies of the object code with a copy of the
- written offer to provide the Corresponding Source. This
- alternative is allowed only occasionally and noncommercially, and
- only if you received the object code with such an offer, in accord
- with subsection 6b.
-
- d) Convey the object code by offering access from a designated
- place (gratis or for a charge), and offer equivalent access to the
- Corresponding Source in the same way through the same place at no
- further charge. You need not require recipients to copy the
- Corresponding Source along with the object code. If the place to
- copy the object code is a network server, the Corresponding Source
- may be on a different server (operated by you or a third party)
- that supports equivalent copying facilities, provided you maintain
- clear directions next to the object code saying where to find the
- Corresponding Source. Regardless of what server hosts the
- Corresponding Source, you remain obligated to ensure that it is
- available for as long as needed to satisfy these requirements.
-
- e) Convey the object code using peer-to-peer transmission, provided
- you inform other peers where the object code and Corresponding
- Source of the work are being offered to the general public at no
- charge under subsection 6d.
-
- A separable portion of the object code, whose source code is excluded
-from the Corresponding Source as a System Library, need not be
-included in conveying the object code work.
-
- A "User Product" is either (1) a "consumer product", which means any
-tangible personal property which is normally used for personal, family,
-or household purposes, or (2) anything designed or sold for incorporation
-into a dwelling. In determining whether a product is a consumer product,
-doubtful cases shall be resolved in favor of coverage. For a particular
-product received by a particular user, "normally used" refers to a
-typical or common use of that class of product, regardless of the status
-of the particular user or of the way in which the particular user
-actually uses, or expects or is expected to use, the product. A product
-is a consumer product regardless of whether the product has substantial
-commercial, industrial or non-consumer uses, unless such uses represent
-the only significant mode of use of the product.
-
- "Installation Information" for a User Product means any methods,
-procedures, authorization keys, or other information required to install
-and execute modified versions of a covered work in that User Product from
-a modified version of its Corresponding Source. The information must
-suffice to ensure that the continued functioning of the modified object
-code is in no case prevented or interfered with solely because
-modification has been made.
-
- If you convey an object code work under this section in, or with, or
-specifically for use in, a User Product, and the conveying occurs as
-part of a transaction in which the right of possession and use of the
-User Product is transferred to the recipient in perpetuity or for a
-fixed term (regardless of how the transaction is characterized), the
-Corresponding Source conveyed under this section must be accompanied
-by the Installation Information. But this requirement does not apply
-if neither you nor any third party retains the ability to install
-modified object code on the User Product (for example, the work has
-been installed in ROM).
-
- The requirement to provide Installation Information does not include a
-requirement to continue to provide support service, warranty, or updates
-for a work that has been modified or installed by the recipient, or for
-the User Product in which it has been modified or installed. Access to a
-network may be denied when the modification itself materially and
-adversely affects the operation of the network or violates the rules and
-protocols for communication across the network.
-
- Corresponding Source conveyed, and Installation Information provided,
-in accord with this section must be in a format that is publicly
-documented (and with an implementation available to the public in
-source code form), and must require no special password or key for
-unpacking, reading or copying.
-
- 7. Additional Terms.
-
- "Additional permissions" are terms that supplement the terms of this
-License by making exceptions from one or more of its conditions.
-Additional permissions that are applicable to the entire Program shall
-be treated as though they were included in this License, to the extent
-that they are valid under applicable law. If additional permissions
-apply only to part of the Program, that part may be used separately
-under those permissions, but the entire Program remains governed by
-this License without regard to the additional permissions.
-
- When you convey a copy of a covered work, you may at your option
-remove any additional permissions from that copy, or from any part of
-it. (Additional permissions may be written to require their own
-removal in certain cases when you modify the work.) You may place
-additional permissions on material, added by you to a covered work,
-for which you have or can give appropriate copyright permission.
-
- Notwithstanding any other provision of this License, for material you
-add to a covered work, you may (if authorized by the copyright holders of
-that material) supplement the terms of this License with terms:
-
- a) Disclaiming warranty or limiting liability differently from the
- terms of sections 15 and 16 of this License; or
-
- b) Requiring preservation of specified reasonable legal notices or
- author attributions in that material or in the Appropriate Legal
- Notices displayed by works containing it; or
-
- c) Prohibiting misrepresentation of the origin of that material, or
- requiring that modified versions of such material be marked in
- reasonable ways as different from the original version; or
-
- d) Limiting the use for publicity purposes of names of licensors or
- authors of the material; or
-
- e) Declining to grant rights under trademark law for use of some
- trade names, trademarks, or service marks; or
-
- f) Requiring indemnification of licensors and authors of that
- material by anyone who conveys the material (or modified versions of
- it) with contractual assumptions of liability to the recipient, for
- any liability that these contractual assumptions directly impose on
- those licensors and authors.
-
- All other non-permissive additional terms are considered "further
-restrictions" within the meaning of section 10. If the Program as you
-received it, or any part of it, contains a notice stating that it is
-governed by this License along with a term that is a further
-restriction, you may remove that term. If a license document contains
-a further restriction but permits relicensing or conveying under this
-License, you may add to a covered work material governed by the terms
-of that license document, provided that the further restriction does
-not survive such relicensing or conveying.
-
- If you add terms to a covered work in accord with this section, you
-must place, in the relevant source files, a statement of the
-additional terms that apply to those files, or a notice indicating
-where to find the applicable terms.
-
- Additional terms, permissive or non-permissive, may be stated in the
-form of a separately written license, or stated as exceptions;
-the above requirements apply either way.
-
- 8. Termination.
-
- You may not propagate or modify a covered work except as expressly
-provided under this License. Any attempt otherwise to propagate or
-modify it is void, and will automatically terminate your rights under
-this License (including any patent licenses granted under the third
-paragraph of section 11).
-
- However, if you cease all violation of this License, then your
-license from a particular copyright holder is reinstated (a)
-provisionally, unless and until the copyright holder explicitly and
-finally terminates your license, and (b) permanently, if the copyright
-holder fails to notify you of the violation by some reasonable means
-prior to 60 days after the cessation.
-
- Moreover, your license from a particular copyright holder is
-reinstated permanently if the copyright holder notifies you of the
-violation by some reasonable means, this is the first time you have
-received notice of violation of this License (for any work) from that
-copyright holder, and you cure the violation prior to 30 days after
-your receipt of the notice.
-
- Termination of your rights under this section does not terminate the
-licenses of parties who have received copies or rights from you under
-this License. If your rights have been terminated and not permanently
-reinstated, you do not qualify to receive new licenses for the same
-material under section 10.
-
- 9. Acceptance Not Required for Having Copies.
-
- You are not required to accept this License in order to receive or
-run a copy of the Program. Ancillary propagation of a covered work
-occurring solely as a consequence of using peer-to-peer transmission
-to receive a copy likewise does not require acceptance. However,
-nothing other than this License grants you permission to propagate or
-modify any covered work. These actions infringe copyright if you do
-not accept this License. Therefore, by modifying or propagating a
-covered work, you indicate your acceptance of this License to do so.
-
- 10. Automatic Licensing of Downstream Recipients.
-
- Each time you convey a covered work, the recipient automatically
-receives a license from the original licensors, to run, modify and
-propagate that work, subject to this License. You are not responsible
-for enforcing compliance by third parties with this License.
-
- An "entity transaction" is a transaction transferring control of an
-organization, or substantially all assets of one, or subdividing an
-organization, or merging organizations. If propagation of a covered
-work results from an entity transaction, each party to that
-transaction who receives a copy of the work also receives whatever
-licenses to the work the party's predecessor in interest had or could
-give under the previous paragraph, plus a right to possession of the
-Corresponding Source of the work from the predecessor in interest, if
-the predecessor has it or can get it with reasonable efforts.
-
- You may not impose any further restrictions on the exercise of the
-rights granted or affirmed under this License. For example, you may
-not impose a license fee, royalty, or other charge for exercise of
-rights granted under this License, and you may not initiate litigation
-(including a cross-claim or counterclaim in a lawsuit) alleging that
-any patent claim is infringed by making, using, selling, offering for
-sale, or importing the Program or any portion of it.
-
- 11. Patents.
-
- A "contributor" is a copyright holder who authorizes use under this
-License of the Program or a work on which the Program is based. The
-work thus licensed is called the contributor's "contributor version".
-
- A contributor's "essential patent claims" are all patent claims
-owned or controlled by the contributor, whether already acquired or
-hereafter acquired, that would be infringed by some manner, permitted
-by this License, of making, using, or selling its contributor version,
-but do not include claims that would be infringed only as a
-consequence of further modification of the contributor version. For
-purposes of this definition, "control" includes the right to grant
-patent sublicenses in a manner consistent with the requirements of
-this License.
-
- Each contributor grants you a non-exclusive, worldwide, royalty-free
-patent license under the contributor's essential patent claims, to
-make, use, sell, offer for sale, import and otherwise run, modify and
-propagate the contents of its contributor version.
-
- In the following three paragraphs, a "patent license" is any express
-agreement or commitment, however denominated, not to enforce a patent
-(such as an express permission to practice a patent or covenant not to
-sue for patent infringement). To "grant" such a patent license to a
-party means to make such an agreement or commitment not to enforce a
-patent against the party.
-
- If you convey a covered work, knowingly relying on a patent license,
-and the Corresponding Source of the work is not available for anyone
-to copy, free of charge and under the terms of this License, through a
-publicly available network server or other readily accessible means,
-then you must either (1) cause the Corresponding Source to be so
-available, or (2) arrange to deprive yourself of the benefit of the
-patent license for this particular work, or (3) arrange, in a manner
-consistent with the requirements of this License, to extend the patent
-license to downstream recipients. "Knowingly relying" means you have
-actual knowledge that, but for the patent license, your conveying the
-covered work in a country, or your recipient's use of the covered work
-in a country, would infringe one or more identifiable patents in that
-country that you have reason to believe are valid.
-
- If, pursuant to or in connection with a single transaction or
-arrangement, you convey, or propagate by procuring conveyance of, a
-covered work, and grant a patent license to some of the parties
-receiving the covered work authorizing them to use, propagate, modify
-or convey a specific copy of the covered work, then the patent license
-you grant is automatically extended to all recipients of the covered
-work and works based on it.
-
- A patent license is "discriminatory" if it does not include within
-the scope of its coverage, prohibits the exercise of, or is
-conditioned on the non-exercise of one or more of the rights that are
-specifically granted under this License. You may not convey a covered
-work if you are a party to an arrangement with a third party that is
-in the business of distributing software, under which you make payment
-to the third party based on the extent of your activity of conveying
-the work, and under which the third party grants, to any of the
-parties who would receive the covered work from you, a discriminatory
-patent license (a) in connection with copies of the covered work
-conveyed by you (or copies made from those copies), or (b) primarily
-for and in connection with specific products or compilations that
-contain the covered work, unless you entered into that arrangement,
-or that patent license was granted, prior to 28 March 2007.
-
- Nothing in this License shall be construed as excluding or limiting
-any implied license or other defenses to infringement that may
-otherwise be available to you under applicable patent law.
-
- 12. No Surrender of Others' Freedom.
-
- If conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot convey a
-covered work so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you may
-not convey it at all. For example, if you agree to terms that obligate you
-to collect a royalty for further conveying from those to whom you convey
-the Program, the only way you could satisfy both those terms and this
-License would be to refrain entirely from conveying the Program.
-
- 13. Use with the GNU Affero General Public License.
-
- Notwithstanding any other provision of this License, you have
-permission to link or combine any covered work with a work licensed
-under version 3 of the GNU Affero General Public License into a single
-combined work, and to convey the resulting work. The terms of this
-License will continue to apply to the part which is the covered work,
-but the special requirements of the GNU Affero General Public License,
-section 13, concerning interaction through a network will apply to the
-combination as such.
-
- 14. Revised Versions of this License.
-
- The Free Software Foundation may publish revised and/or new versions of
-the GNU General Public License from time to time. Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
- Each version is given a distinguishing version number. If the
-Program specifies that a certain numbered version of the GNU General
-Public License "or any later version" applies to it, you have the
-option of following the terms and conditions either of that numbered
-version or of any later version published by the Free Software
-Foundation. If the Program does not specify a version number of the
-GNU General Public License, you may choose any version ever published
-by the Free Software Foundation.
-
- If the Program specifies that a proxy can decide which future
-versions of the GNU General Public License can be used, that proxy's
-public statement of acceptance of a version permanently authorizes you
-to choose that version for the Program.
-
- Later license versions may give you additional or different
-permissions. However, no additional obligations are imposed on any
-author or copyright holder as a result of your choosing to follow a
-later version.
-
- 15. Disclaimer of Warranty.
-
- THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
-APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
-OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
-IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
-ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
- 16. Limitation of Liability.
-
- IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
-THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
-GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
-USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
-DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
-PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
-EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGES.
-
- 17. Interpretation of Sections 15 and 16.
-
- If the disclaimer of warranty and limitation of liability provided
-above cannot be given local legal effect according to their terms,
-reviewing courts shall apply local law that most closely approximates
-an absolute waiver of all civil liability in connection with the
-Program, unless a warranty or assumption of liability accompanies a
-copy of the Program in return for a fee.
-
- END OF TERMS AND CONDITIONS
-
- How to Apply These Terms to Your New Programs
-
- If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
- To do so, attach the following notices to the program. It is safest
-to attach them to the start of each source file to most effectively
-state the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
- {one line to give the program's name and a brief idea of what it does.}
- Copyright (C) {year} {name of author}
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-
-Also add information on how to contact you by electronic and paper mail.
-
- If the program does terminal interaction, make it output a short
-notice like this when it starts in an interactive mode:
-
- {project} Copyright (C) {year} {fullname}
- This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
- This is free software, and you are welcome to redistribute it
- under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License. Of course, your program's commands
-might be different; for a GUI interface, you would use an "about box".
-
- You should also get your employer (if you work as a programmer) or school,
-if any, to sign a "copyright disclaimer" for the program, if necessary.
-For more information on this, and how to apply and follow the GNU GPL, see
-.
-
- The GNU General Public License does not permit incorporating your program
-into proprietary programs. If your program is a subroutine library, you
-may consider it more useful to permit linking proprietary applications with
-the library. If this is what you want to do, use the GNU Lesser General
-Public License instead of this License. But first, please read
-.
-
diff --git a/app/src/main/lib/sweetblue/README.md b/app/src/main/lib/sweetblue/README.md
deleted file mode 100644
index 08d483a6..00000000
--- a/app/src/main/lib/sweetblue/README.md
+++ /dev/null
@@ -1,226 +0,0 @@
-| Why?
-| Features
-| Getting Started
-| Licensing
-| Wiki
-| Toolbox
-
-
-
-
-
-
-
-
-
-
-Why?
-====
-
-Android's BLE stack has some...issues...
-
-* https://github.com/iDevicesInc/SweetBlue/wiki/Android-BLE-Issues
-* https://code.google.com/p/android/issues/detail?id=58381
-* http://androidcommunity.com/nike-blames-ble-for-their-shunning-of-android-20131202/
-* http://stackoverflow.com/questions/17870189/android-4-3-bluetooth-low-energy-unstable
-
-SweetBlue is a blanket abstraction that shoves all that troublesome behavior behind a clean interface and gracefully degrades when the underlying stack becomes too unstable for even it to handle.
-
-It’s built on the hard-earned experience of several commercial BLE projects and provides so many transparent workarounds to issues both annoying and fatal that it’s frankly impossible to imagine writing an app without it. It also supports many higher-level constructs, things like atomic transactions for coordinating authentication handshakes and firmware updates, flexible scanning configurations, read polling, transparent retries for transient failure conditions, and, well, the list goes on. The API is dead simple, with usage dependence on a few plain old Java objects and link dependence on standard Android classes. It offers conveniences for debugging and analytics and error handling that will save you months of work - last mile stuff you didn't even know you had to worry about.
-
-Features
-========
-
-* Full-coverage API documentation: http://idevicesinc.com/sweetblue/docs/api
-* Sample applications.
-* Battle-tested in commercial apps.
-* Plain old Java with zero API-level dependencies.
-* Rich, queryable state tracking that makes UI integration a breeze.
-* Automatic service discovery.
-* Full support for server role including advertising.
-* Easy RSSI tracking with built-in polling and caching, including distance and friendly signal strength calculations.
-* Highly configurable scanning with min/max time limits, periodic bursts, advanced filtering, and more.
-* Continuous scanning mode that saves battery and defers to more important operations by stopping and starting as needed under the hood.
-* Atomic transactions for easily coordinating authentication handshakes, initialization, and firmware updates.
-* Automatic striping of characteristic writes greater than [MTU](http://en.wikipedia.org/wiki/Maximum_transmission_unit) size of 20 bytes.
-* Undiscovery based on last time seen.
-* Clean leakage of underlying native stack objects in case of emergency.
-* Wraps Android API level checks that gate certain methods.
-* Verbose [logging](https://github.com/iDevicesInc/SweetBlue/wiki/Logging) that outputs human-readable thread IDs, UUIDs, status codes and states instead of alphabet soup.
-* Wrangles a big bowl of thread spaghetti behind a nice asynchronous API - make a call on main thread, get a callback on main thread a short time later.
-* Internal priority job queue that ensures serialization of all operations so native stack doesn’t get overloaded and important stuff gets done first.
-* Optimal coordination of the BLE stack when connected to multiple devices.
-* Detection and correction of dozens of BLE failure conditions.
-* Numerous manufacturer-specific workarounds and hacks all hidden from you.
-* Built-in polling for read characteristics with optional change-tracking to simulate notifications.
-* Transparent retries for transient failure conditions related to connecting, getting services, and scanning.
-* Comprehensive callback system with clear enumerated reasons when something goes wrong like connection or read/write failures.
-* Distills dozens of lines of boilerplate, booby-trapped, native API usages into single method calls.
-* Transparently falls back to Bluetooth Classic for certain BLE failure conditions.
-* On-the-fly-configurable reconnection loops started automatically when random disconnects occur, e.g. from going out of range.
-* Retention and automatic reconnection of devices after BLE off->on cycle or even complete app reboot.
-* One convenient method to completely unwind and reset the Bluetooth stack.
-* Detection and reporting of BLE failure conditions that user should take action on, such as restarting the Bluetooth stack or even the entire phone.
-* Runtime analytics for tracking average operation times, total elapsed times, and time estimates for long-running operations like firmware updates.
-
-
-Getting Started
-===============
-1. If using **Android Studio** or **Gradle**...
- 1. [Download](http://idevicesinc.com/sweetblue/#tryit) the latest release to a subfolder of your project such as `MyApp/src/main/lib/`. This ZIP contains several samples, precompiled JARS, and API docs and is preferable to downloading from GitHub, which only contains the raw source.
- 2. Open the app module's `build.gradle` file.
- 3. If building with source, your gradle file should look something like this:
-
- ```
-
- android {
- compileSdkVersion 25
- buildToolsVersion '25.0.3'
-
- defaultConfig {
- minSdkVersion 18
- targetSdkVersion 25
- ...
- }
-
- sourceSets {
- main.java.srcDirs += 'src/main/lib/sweetblue/src'
- main.res.srcDirs += 'src/main/lib/sweetblue/res'
- ...
- }
- ...
- }
-
- ```
-
- 4. If you're building with source from github, the sourceSet path is a bit different:
-
- ```
-
- android {
- compileSdkVersion 25
- buildToolsVersion '25.0.3'
-
- defaultConfig {
- minSdkVersion 18
- targetSdkVersion 25
- ...
- }
-
- sourceSets {
- main.java.srcDirs += 'src/main/lib/sweetblue/library/src/main/java'
- main.res.srcDirs += 'src/main/lib/sweetblue/library/src/main/res'
- ...
- }
- ...
- }
-
- ```
-
- 5. Else if building with JAR, it should look something like this:
-
- ```
-
- android {
- compileSdkVersion 25
- buildToolsVersion '25.0.3'
-
- defaultConfig {
- minSdkVersion 18
- targetSdkVersion 25
- ...
- }
-
- dependencies {
- compile fileTree(dir: 'libs', include: '*.jar')
- ...
- }
- ...
- }
-
- ```
-
-2. Now add these to the root of `MyApp/AndroidManifest.xml`:
-
- ```
-
-
-
-
-
-
-
-
-
-
- ```
-
-3. From your `Activity` or `Service` or `Application` instance, this is all it takes to discover a device, connect to it, and read a characteristic:
- ```
- // A ScanFilter decides whether a BleDevice instance will be created from a
- // BLE advertisement and passed to the DiscoveryListener implementation below.
- final ScanFilter scanFilter = new ScanFilter()
- {
- @Override public Please onEvent(ScanEvent e)
- {
- return Please.acknowledgeIf(e.name_normalized().contains("my_device"))
- .thenStopScan();
- }
- };
-
- // New BleDevice instances are provided through this listener.
- // Nested listeners then listen for connection and read results.
- // Obviously you will want to structure your actual code a little better.
- // The deep nesting simply demonstrates the async-callback-based nature of the API.
- final DiscoveryListener discoveryListener = new DiscoveryListener()
- {
- @Override public void onEvent(DiscoveryEvent e)
- {
- if( e.was(LifeCycle.DISCOVERED) )
- {
- e.device().connect(new StateListener()
- {
- @Override public void onEvent(StateEvent e)
- {
- if( e.didEnter(BleDeviceState.INITIALIZED) )
- {
- e.device().read(Uuids.BATTERY_LEVEL, new ReadWriteListener()
- {
- @Override public void onEvent(ReadWriteEvent e)
- {
- if( e.wasSuccess() )
- {
- Log.i("", "Battery level is " + e.data_byte() + "%");
- }
- }
- });
- }
- }
- });
- }
- }
-};
-
- // This class helps you navigate the treacherous waters of Android M Location requirements for scanning.
- // First it enables bluetooth itself, then location permissions, then location services. The latter two
- // are only needed in Android M. This must be called from an Activity instance.
- BluetoothEnabler.start(this, new DefaultBluetoothEnablerFilter()
- {
- @Override public Please onEvent(BluetoothEnablerEvent e)
- {
- if( e.isDone() )
- {
- e.bleManager().startScan(scanFilter, discoveryListener);
- }
-
- return super.onEvent(e);
- }
- });
- ```
-
-
-Licensing
-=========
-
-SweetBlue is released here under the [GPLv3](http://www.gnu.org/copyleft/gpl.html). Please visit http://idevicesinc.com/sweetblue for proprietary licensing options. In a nutshell, if you're developing a for-profit commercial app you may use this library for free for evaluation purposes, but most likely your use case will require purchasing a proprietary license before you can release your app to the public. See the [FAQ](https://github.com/iDevicesInc/SweetBlue/wiki/FAQ) for more details and https://tldrlegal.com/license/gnu-general-public-license-v3-%28gpl-3%29 for a general overview of the GPL.
-
diff --git a/app/src/main/lib/sweetblue/library/.gitignore b/app/src/main/lib/sweetblue/library/.gitignore
deleted file mode 100644
index 796b96d1..00000000
--- a/app/src/main/lib/sweetblue/library/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
diff --git a/app/src/main/lib/sweetblue/library/build.gradle b/app/src/main/lib/sweetblue/library/build.gradle
deleted file mode 100644
index ff4083b6..00000000
--- a/app/src/main/lib/sweetblue/library/build.gradle
+++ /dev/null
@@ -1,701 +0,0 @@
-plugins {
- id 'org.hidetake.ssh' version '1.1.4'
-}
-
-apply plugin: 'com.android.library'
-apply plugin: 'checkstyle'
-apply plugin: 'maven'
-apply plugin: 'signing'
-
-
-version = "${SEMVER}".replace("_", ".")
-
-android {
- compileSdkVersion 26
- buildToolsVersion '26.0.2'
-
- defaultConfig {
- minSdkVersion 18
- project.archivesBaseName = "sweetblue"
- }
-
- buildTypes {
- release {
- minifyEnabled false
- }
- debug {
- minifyEnabled false
- }
- }
-
- // Commenting out Java 8 support for now until the lambda issue can be fixed
- // See https://issuetracker.google.com/issues/68977385
-// compileOptions {
-// sourceCompatibility = '1.8'
-// targetCompatibility = '1.8'
-// }
-}
-
-// Set base variables
-ext.JAR_BASE_NAME = "sweetblue_${SEMVER}"
-ext.BUNDLE_FOLDER = projectDir.absolutePath + "/" + STAGE + "/" + JAR_BASE_NAME
-ext.JAR_DIR = BUNDLE_FOLDER + "/jars"
-ext.JAR_NAME = JAR_BASE_NAME + ".jar"
-ext.JAVADOC_JAR_NAME = JAR_BASE_NAME + "-javadoc.jar"
-ext.SOURCES_JAR_NAME = JAR_BASE_NAME + "-sources.jar"
-ext.AS_BUILD_SUFFIX = "/android_studio/build"
-ext.AS_APP_BUILD_SUFFIX = "/android_studio/app/build"
-ext.AS_SUFFIX = "/android_studio/sweetblue"
-ext.ECLIPSE_SUFFIX = "/eclipse/lib"
-
-
-// List of all of the modules' build.gradle files
-def moduleFiles = files(rootDir.getAbsolutePath() + "/integration/build.gradle") \
- + files(rootDir.absolutePath + "/library/build.gradle") \
- + files(rootDir.absolutePath + "/sweetunit/build.gradle") \
- + files(rootDir.absolutePath + "/tester/build.gradle") \
- + files(rootDir.absolutePath + "/lint.gradle")
-
-repositories {
- mavenCentral()
- google()
-}
-
-dependencies {
-}
-
-// Task definitions
-task checker(type: Checkstyle) {
- configFile file("../scripts/config/checkstyle/checkstyle.xml")
- source '../src'
- include '**/*.java'
- exclude '**/gen/**'
- classpath = files()
-}
-
-// Creates Sweetblue jar
-task jar(type: Jar, dependsOn: "compileReleaseSources") {
- from 'build/intermediates/classes/release'
- archiveName = JAR_NAME
- destinationDir = new File(JAR_DIR)
- group = "sweetblue"
- description = "Generates a Sweetblue jar."
-}
-
-// Create the source Jar
-task sourceJar(type: Jar, dependsOn: jar) {
- archiveName = SOURCES_JAR_NAME
- classifier = 'sources'
- from android.sourceSets.main.java.sourceFiles
- destinationDir = new File(JAR_DIR)
- group = "sweetblue"
- description = "Generates SweetBlue sources jar."
-}
-
-// disable the crazy super-strict doclint tool in Java 8
-if (JavaVersion.current().isJava8Compatible()) {
- tasks.withType(Javadoc) {
- options.addStringOption('Xdoclint:none', '-quiet')
- }
-}
-
-// Generate javadocs
-task gendocs(type: Javadoc) {
- String path = rootDir.absolutePath + "/scripts/assets/doc_style.css"
- options.stylesheetFile = new File(path)
- options.windowTitle = "SweetBlue"
- options.memberLevel = JavadocMemberLevel.PROTECTED
- options.author = true
- //exclude '**/backend/**'
- //excludes = ["com.idevicesinc.sweetblue.backend"]
- String home = android.getSdkDirectory().getAbsolutePath()
- options.linksOffline('http://d.android.com/reference', home + '/docs/reference')
- String v = "${SEMVER}"
- version = v.replace("_", '.')
- options.setDocTitle("SweetBlue " + version + " API")
- destinationDir = new File("${BUNDLE_FOLDER}/docs/api")
- source = android.sourceSets.main.java.srcDirs
- classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
-
- // Check for warnings, and cause build to fail if there are any.
- // This is a temporary solution until a more official solution
- // makes it's way into Gradle.
- def outputEvents = []
- def listener = new StandardOutputListener() {
- void onOutput(CharSequence output) {
- outputEvents << output
- }
- }
- doFirst {
- getLogging().addStandardOutputListener(listener)
- }
- doLast {
- getLogging().removeStandardOutputListener(listener)
- outputEvents.each { e ->
- if (e =~ " warning -") {
- throw new GradleException("You have some javadoc warnings, please fix them!")
- }
- }
- }
- group = "sweetblue"
-}
-
-// Create javadoc jar file
-task javadocJar(type: Jar, dependsOn: gendocs) {
- archiveName = JAVADOC_JAR_NAME
- classifier = 'javadoc'
- from gendocs.destinationDir
- destinationDir = new File(JAR_DIR)
- group = "sweetblue"
- description = "Generates the javadoc jar."
-}
-
-// Copy the src and res directories, and Readme file
-task copyFiles {
- group = "sweetblue"
-
- doLast {
- checkDir("${BUNDLE_FOLDER}")
- copy {
- from "src/main/java"
- into "${BUNDLE_FOLDER}/src"
- }
- copy {
- from "src/main/res"
- into "${BUNDLE_FOLDER}/res"
- }
- copy {
- from "../README.md"
- into BUNDLE_FOLDER
- }
- }
-}
-
-static def checkDir(String dir) {
- File f = new File(dir)
- if (!f.exists()) {
- f.mkdirs()
- }
-}
-
-// Pull down the samples repo
-task getSamples(dependsOn: [jar, sourceJar, javadocJar, "assembleRelease"]) {
- group = "sweetblue"
-
- doLast {
- // If the directory already exists, wipe it out, and start over
- // again, so this doesn't fail the build
- String samplePath = projectDir.absolutePath + "/${STAGE}/samples"
- File f = new File(samplePath)
- if (f.exists()) {
- delete "${samplePath}"
- }
-
- // When running on the build server, we need to inject the username and password
- // otherwise it will pull the un/pw of the current user.
- String gu = System.getenv("G_UNAME")
- def r
- if (!gu || !gu.trim()) {
- r = "git clone https://bitbucket.org/idevices/sweetblue_samples.git ${samplePath}".execute()
- } else {
- String run = "git clone https://" + System.getenv("G_UNAME") + ":" + System.getenv("G_PW") + "@bitbucket.org/idevices/sweetblue_samples.git ${samplePath}"
- r = run.execute()
- }
- r.waitFor()
- String v = r.exitValue()
- if (!v.equals("0")) {
- throw new RuntimeException(r.errorStream.text)
- }
- }
-}
-
-
-ext.mainJar = "${BUNDLE_FOLDER}/jars/${JAR_BASE_NAME}.jar"
-ext.sourcesJar = "${BUNDLE_FOLDER}/jars/${JAR_BASE_NAME}-sources.jar"
-ext.javadocJar = "${BUNDLE_FOLDER}/jars/${JAR_BASE_NAME}-javadoc.jar"
-ext.mainAar = "build/outputs/aar/sweetblue-release.aar"
-
-// Copy the samples to the bundle folder
-task copySamples(dependsOn: [copyFiles, getSamples]) {
- group = "sweetblue"
-
- doLast {
- String d = "${STAGE}/samples/samples"
- copy {
- from d
- into "${BUNDLE_FOLDER}/samples"
- }
- }
-}
-
-// Creates a new gradle build task for the input build.gradle file
-def buildSampleTask(buildDir) {
- return tasks.create("build${buildDir}SampleTask", GradleBuild) {
- buildFile = "${buildDir}/android_studio/app/build.gradle"
- tasks = ['assemble']
- }
-}
-
-// Copy the new sweetblue jar, sourcejar, and javadoc jars to each sample AS
-// project, and build
-// Copy the aar file to the bundle folder for inclusion in the zip file
-task copyAndRenameJars(dependsOn: copySamples) {
- group = "sweetblue"
-
- doLast {
- File sampleDir = new File("${BUNDLE_FOLDER}/samples/")
- FileCollection collection = files {
- sampleDir.listFiles(new FileFilter() {
- @Override
- boolean accept(File file) {
- return !file.isHidden()
- }
- })
- }
- collection.each { File file ->
- copy {
- from mainAar
- into file.getAbsolutePath() + AS_SUFFIX
- }
- copy {
- from mainJar
- into file.getAbsolutePath() + ECLIPSE_SUFFIX
- rename("_${SEMVER}", "")
- }
- copy {
- from sourcesJar
- into file.getAbsolutePath() + ECLIPSE_SUFFIX
- rename("_${SEMVER}", "")
- }
- copy {
- from javadocJar
- into file.getAbsolutePath() + AS_SUFFIX
- rename("_${SEMVER}", "")
- }
- copy {
- from javadocJar
- into file.getAbsolutePath() + ECLIPSE_SUFFIX
- rename("_${SEMVER}", "")
- }
- copy {
- from '../local.properties'
- into file.getAbsolutePath() + "/android_studio"
- }
-// File outerBuildFile = new File(file.getAbsolutePath() + "/android_studio/build.gradle")
-// String contents = outerBuildFile.getText("UTF-8")
-// contents = contents.replace("classpath", "// classpath")
-// outerBuildFile.write(contents, "UTF-8")
- // TODO - Fix this! With the latest APIs, this no longer works.
-// buildSampleTask(file.getAbsolutePath()).execute()
-// contents = contents.replace("// classpath", "classpath")
-// outerBuildFile.write(contents, "UTF-8")
- delete(file.getAbsolutePath() + "/android_studio/local.properties")
-
- // Delete build folders to save on space in the resulting zip file
- delete(file.getAbsolutePath() + AS_APP_BUILD_SUFFIX)
- delete(file.getAbsolutePath() + AS_BUILD_SUFFIX)
- }
- copy {
- from mainAar
- into "${BUNDLE_FOLDER}/aar/"
- }
- }
-}
-
-task copyGPL(dependsOn: copyAndRenameJars) {
- group = "sweetblue"
-
- doLast {
- copy {
- from "../LICENSE"
- into "${BUNDLE_FOLDER}"
- }
- delete "${BUNDLE_FOLDER}/sweetblue_commercial_license.pdf"
- }
-}
-
-ext.SB_COMM_LICENSE = System.getenv("SB_COMM_LICENSE")
-
-task copyCommercialLicense(dependsOn: copyAndRenameJars) {
- group = "sweetblue"
-
- doLast {
- copy {
- from "${SB_COMM_LICENSE}"
- into "${BUNDLE_FOLDER}"
- }
- delete "${BUNDLE_FOLDER}/LICENSE"
- }
-}
-
-task zipTrial(type: Zip, dependsOn: copyGPL) {
- from "${BUNDLE_FOLDER}"
- archiveName = "sweetblue.zip"
- destinationDir = new File(projectDir.absolutePath + "/${STAGE}")
- group = "sweetblue"
-}
-
-task zipCommercial(type: Zip, dependsOn: copyCommercialLicense) {
- from "${BUNDLE_FOLDER}"
- archiveName = "sweetblue_commercial.zip"
- destinationDir = new File(projectDir.absolutePath + "/${STAGE}")
- group = "sweetblue"
-}
-
-task zipJar(dependsOn: [zipTrial, zipCommercial]) {
- group = "sweetblue"
- description = "Pulls down samples, builds them, and zips up all SweetBlue files."
-
- doLast {
- copy {
- from projectDir.absolutePath + "/${STAGE}/sweetblue.zip"
- into projectDir.absolutePath + "/${STAGE}"
- rename { String fileName ->
- fileName.replace("sweetblue.zip", "${JAR_BASE_NAME}.zip")
- }
- }
- }
-}
-
-static String getHexString(byte[] data) {
- def hex = "0123456789abcdef".toCharArray()
- StringBuilder b = new StringBuilder()
- for (int i = 0; i < data.length; i++) {
- int v = data[i] & 0xFF
- b.append(hex[v >>> 4])
- b.append(hex[v & 0x0F])
- }
- return b.toString().trim()
-}
-
-import java.security.MessageDigest
-
-task outputVersion {
- group = "sweetblue"
- description = 'Outputs the current version to version.txt file, for build server purposes.'
- doLast {
- File ver = new File('version.txt')
- ver.write(version, 'UTF-8')
- }
-}
-
-task incrementVersion() {
- doLast {
- String[] vers = "${version}".split('\\.')
- int release = Integer.parseInt(vers[2])
- release++
- String newVersion = vers[0] + "_" + vers[1] + "_" + release
- File buildFile = new File(rootDir.absolutePath + "/gradle.properties")
- String contents = buildFile.getText('UTF-8')
- contents = contents.replaceAll("SEMVER=.*", "SEMVER=${newVersion}")
- buildFile.write(contents, 'UTF-8')
- }
-}
-
-task bumpVersion {
- group = "sweetblue"
- description = "Updates readme file with the latest version number, and updates hash in Uuids.java"
- doLast {
- MessageDigest md = MessageDigest.getInstance("SHA1")
- byte[] res = md.digest(version.getBytes())
- String hash = getHexString(res)
- File uuids = new File(projectDir.absolutePath + "/src/main/java/com/idevicesinc/sweetblue/utils/Uuids.java")
- String contents = uuids.getText('UTF-8')
- contents = contents.replaceAll("BLUETOOTH_CONNECTED_HASH = \".*\"", "BLUETOOTH_CONNECTED_HASH = \"${hash}\"")
- uuids.write(contents, 'UTF-8')
- File readme = new File(rootDir.absolutePath + "/README.md")
- contents = readme.getText('UTF-8')
- contents = contents.replaceAll("version-.*-blue", "version-${version}-blue")
- contents = contents.replaceAll("sweetblue:.*'", "sweetblue:${version}'")
- readme.write(contents, 'UTF-8')
- }
-}
-
-task createAndPushGitTag {
- group = "sweetblue"
- description = 'Creates a tag based on the current version and pushes it to origin'
- doLast {
- exec {
- commandLine 'git', 'tag', "-a", "v${version}", "-m", "\"${version} Release\""
- }
- exec {
- commandLine 'git', "push", "origin", "v${version}"
- }
- }
-}
-
-task gitAddCommitPush {
- // Takes parameter "message" for git commit message
- group = "sweetblue"
- description = "Adds, commits with message, and pushes all tracked files to git"
- doLast {
- // git add -u
- String gu = System.getenv("G_UNAME")
- def r1 = "git add -u".execute()
- r1.waitFor()
- String v1 = r1.exitValue()
- if(!v1.equals("0"))
- throw new RuntimeException(r1.errorStream.text)
-
- // git commit -m "$message"
- String commit = "git commit -m \"" + message + "\""
- println(commit)
- def r2 = (commit).execute()
- r2.waitFor()
- String v2 = r2.exitValue()
- if(!v2.equals("0"))
- {
- throw new RuntimeException(r2.errorStream.text)
- }
-
- def r3 = ("git push origin HEAD").execute() // Push to current head
- r3.waitFor()
- String v3 = r3.exitValue()
- if(!v3.equals("0"))
- {
- throw new RuntimeException(r3.errorStream.text)
- }
- }
-}
-
-task bumpCompileSdkVersion (dependsOn: getSamples) {
- // Takes parameter compileSdkVersion (e.g. 25)
- group = "sweetblue"
- description = "Updates compileSdkVersion and targetSdkVersion in all inner project's build.gradle"
- doLast {
- def allFiles = moduleFiles
-
- // Add samples to allFiles
- File samplesDir = file(projectDir.absolutePath + "/${STAGE}/samples/samples")
- def samples = files { samplesDir.listFiles() }
- samples.each {File sample ->
- allFiles.add files(sample.absolutePath + "/android_studio/app/build.gradle") }
- println allFiles.asFileTree.files
-
- allFiles.each { File f ->
- String contents = f.getText('UTF-8')
- contents = contents.replaceAll("compileSdkVersion \\d+", "compileSdkVersion " + compileSdkVersion) // Replace compileSdkVersion
- contents = contents.replaceAll("targetSdkVersion \\d+", "targetSdkVersion " + compileSdkVersion) // Replace targetSdkVersion
- f.write(contents, 'UTF-8')
- }
- }
-}
-
-task bumpGradleVersion (dependsOn: getSamples) {
- // Takes parameter gradleVersion (e.g. 3.3)
- group = "sweetblue"
- description = "Updates the Gradle distribution version in all inner project's build.gradle"
- doLast {
- def allFiles = files(rootDir.getAbsolutePath() + "/gradle/wrapper/gradle-wrapper.properties") + files()
-
- File samplesDir = file(projectDir.absolutePath + "/${STAGE}/samples/samples")
- def samples = files { samplesDir.listFiles() }
- samples.each {File sample ->
- allFiles.add files(sample.absolutePath + "/android_studio/gradle/wrapper/gradle-wrapper.properties") }
- println allFiles.asFileTree.files
-
- allFiles.each { File f ->
- String contents = f.getText('UTF-8')
- contents = contents.replaceAll("services.gradle.org/distributions/.*zip", "services.gradle.org/distributions/gradle-" + gradleVersion + "-all.zip") // Replace the Gradle distro version
- f.write(contents, 'UTF-8')
- }
- }
-}
-
-task bumpGradlePluginVersion (dependsOn: getSamples) {
- // Takes parameter gradlePluginVersion (e.g. 2.3.3)
- group = "sweetblue"
- description = "Updates the Gradle plugin version in all inner project's build.gradle"
- doLast {
- def allFiles = files(rootDir.absolutePath + "/build.gradle") \
- + files(rootDir.absolutePath + "/lint.gradle")
-
- File samplesDir = file(projectDir.absolutePath + "/${STAGE}/samples/samples")
- def samples = files { samplesDir.listFiles() }
- samples.each {File sample ->
- allFiles.add files(sample.absolutePath + "/android_studio/build.gradle") }
- println allFiles.asFileTree.files
-
- allFiles.each { File f ->
- String contents = f.getText('UTF-8')
- contents = contents.replaceAll("'com.android.tools.build:gradle:.*'", "'com.android.tools.build:gradle:" + gradlePluginVersion + "'") // Replace the Gradle plugin version
- f.write(contents, 'UTF-8')
- }
- }
-}
-
-
-task cleanFolders {
- doLast {
- delete "${STAGE}/${STANDARD_DIR}", "${STAGE}/${PRO_DIR}", "build"
- }
-}
-
-
-def checkStyleWarningsFile = 'build/reports/checkstyle/checkstyle.xml'
-
-task verifyNoCheckstyleWarnings(type: GradleBuild) {
- doLast {
- File warningsFile = file(checkStyleWarningsFile)
- if (warningsFile.exists() && warningsFile.text.contains("
- if (taskGraph.allTasks.any { it instanceof Sign }) {
-
- allprojects { ext."signing.keyId" = System.getenv("SIGN_ID") }
- allprojects { ext."signing.secretKeyRingFile" = System.getenv("SIGN_RING") }
- allprojects { ext."signing.password" = System.getenv("SIGN_PW") }
- }
-}
-
-uploadArchives {
- repositories {
- mavenDeployer {
- beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
-
- pom.groupId = 'com.idevicesinc'
- pom.artifactId = 'sweetblue'
-
-
- repository(url: "${ARCHIVA_URL}") {
- authentication(userName: "${a_user}", password: "${a_pw}")
- }
-
- pom.project {
- name 'SweetBlue'
- packaging 'aar'
- description 'Android Bluetooth (BLE) library'
- url 'http://idevicesinc.com/sweetblue'
- }
- }
- }
-}
-
-task uploadInternal(type: Upload) {
- group = "upload"
- description = "Uploads artifact to our internal maven repository"
- configuration = uploadArchives.configuration
- uploadDescriptor = true
- repositories {
- mavenDeployer {
- beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
-
- pom.groupId = 'com.idevicesinc'
- pom.artifactId = 'sweetblue'
-
- repository(url: "http://10.0.1.239:5819/repository/internal") {
- authentication(userName: "${a_user}", password: "${a_pw}")
- }
-
- pom.project {
- name 'SweetBlue'
- packaging 'aar'
- description 'Android Bluetooth (BLE) library'
- url 'http://idevicesinc.com/sweetblue'
- }
- }
- }
-}
-
-// This is a hack to avoid signing archives when SweetBlue is run as a submodule within another project. Obviously, we need to sign to deploy,
-// so we apply the signing.gradle file (which the build server swaps out before building, then clears the file when done).
-apply from: 'signing.gradle'
-
-task androidJavadocs(type: Javadoc) {
- source = android.sourceSets.main.java.srcDirs
- classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
-}
-
-remotes {
- webServer {
- host = System.getenv("SWEETBLUE_SERVER_ADDRESS")
- user = System.getenv("SWEETBLUE_COM_FTP_USERNAME")
- password = System.getenv("SWEETBLUE_COM_FTP_PASSWORD")
- }
-}
-
-task uploadWebServerZips(dependsOn: zipJar) {
- group = "upload"
- description = "Upload zip files to iDevices public web server."
-
- doLast {
- ssh.run {
- session(remotes.webServer) {
- put from: projectDir.absolutePath + "/${STAGE}/${JAR_BASE_NAME}.zip", into: '/var/www/html/sweetblue/downloads'
- put from: projectDir.absolutePath + "/${STAGE}/sweetblue.zip", into: '/var/www/html/sweetblue/downloads'
- }
- }
- }
-}
-
-task uploadCommerceZip(dependsOn: zipJar) {
- group = "upload"
- description = "Upload zip file to iDevices big commerce server."
-
- doLast {
- // Upload zip file to the big commerce site
- def curlS = ['curl', '-T', projectDir.absolutePath + "/${STAGE}/sweetblue_commercial.zip", "--digest", "https://sweetblue%40idevicesinc.com:${COMMERCE_PW}@solutions.idevicesinc.com/dav/product_downloads/s/sweetblue.zip"]
- def curl = curlS.execute()
- curl.waitFor()
- if (curl.exitValue() != 0) {
- throw new RuntimeException("Failed to upload zip file to Big Commerce! Error: " + curl.errorStream.text)
- }
- }
-}
-
-task uploadZips(dependsOn: [uploadWebServerZips, uploadCommerceZip]) {
- group = "upload"
- description = "Upload zip files to iDevices public web and big commerce servers."
-}
-
-task uploadDocs(dependsOn: gendocs) {
- group = "upload"
- description = "Upload docs to iDevices public web server."
-
- doLast {
- ssh.run {
- session(remotes.webServer) {
- put from: "/${BUNDLE_FOLDER}/docs/api", into: '/var/www/html/sweetblue/docs'
- }
- }
- }
-}
-
-task uploadToIDevServer(dependsOn: [uploadZips, uploadDocs]) {
- group = "upload"
- description = "Upload zip files and docs to iDevices server."
-}
-
-artifacts {
- archives sourceJar
-}
\ No newline at end of file
diff --git a/app/src/main/lib/sweetblue/library/proguard-rules.pro b/app/src/main/lib/sweetblue/library/proguard-rules.pro
deleted file mode 100644
index eb0438e0..00000000
--- a/app/src/main/lib/sweetblue/library/proguard-rules.pro
+++ /dev/null
@@ -1,17 +0,0 @@
-# Add project specific ProGuard rules here.
-# By default, the flags in this file are appended to flags specified
-# in /Users/ryanbis/Library/Android/sdk/tools/proguard/proguard-android.txt
-# You can edit the include path and order by changing the proguardFiles
-# directive in build.gradle.
-#
-# For more details, see
-# http://developer.android.com/guide/developing/tools/proguard.html
-
-# Add any project specific keep options here:
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-# public *;
-#}
diff --git a/app/src/main/lib/sweetblue/library/signing.gradle b/app/src/main/lib/sweetblue/library/signing.gradle
deleted file mode 100644
index e69de29b..00000000
diff --git a/app/src/main/lib/sweetblue/library/signing_server.gradle b/app/src/main/lib/sweetblue/library/signing_server.gradle
deleted file mode 100644
index dba7e31f..00000000
--- a/app/src/main/lib/sweetblue/library/signing_server.gradle
+++ /dev/null
@@ -1,4 +0,0 @@
-signing {
- required { gradle.taskGraph.hasTask("uploadArchives") }
- sign configurations.archives
-}
\ No newline at end of file
diff --git a/app/src/main/lib/sweetblue/library/src/main/AndroidManifest.xml b/app/src/main/lib/sweetblue/library/src/main/AndroidManifest.xml
deleted file mode 100644
index fc122c65..00000000
--- a/app/src/main/lib/sweetblue/library/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/lib/sweetblue/library/src/main/res/values-cs/strings.xml b/app/src/main/lib/sweetblue/library/src/main/res/values-cs/strings.xml
deleted file mode 100755
index 8917877b..00000000
--- a/app/src/main/lib/sweetblue/library/src/main/res/values-cs/strings.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
- Zakázání přístupu ke službám zjišťování polohy znamená, že nebude fungovat nízkoenergetické vyhledávání.
- Aplikace vyžaduje v souboru AndroidManifest.xml oprávnění android.permission.ACCESS_COARSE_LOCATION nebo android.permission.ACCESS_FINE_LOCATION!
- Klikněte na tlačítko Oprávnění, povolte položku Poloha a stiskněte dvakrát tlačítko Zpět.
- Systém Android Marshmallow (6.0+) požaduje k vyhledávání zařízení Bluetooth oprávnění Poloha. Kliknutím na tlačítko Přijmout povolte oprávnění Poloha.
- Systém Android Marshmallow (6.0+) požaduje, aby aplikace, která vyhledává zařízení Bluetooth, měla oprávnění Poloha.\n\nSystém Marshmallow rovněž požaduje ke zlepšení zjišťování zařízení Bluetooth služby zjišťování polohy. Tyto služby nejsou nutné k používání této aplikace, ale jsou doporučeny k lepšímu zjišťování zařízení.\n\nKliknutím na tlačítko Přijmout povolte oprávnění Poloha a služby zjišťování polohy.
- Systém Android Marshmallow (6.0+) požaduje ke zlepšení vyhledávání zařízení Bluetooth služby zjišťování polohy. Tyto služby nejsou nutné, ale je doporučeno je zapnout, aby bylo možné zlepšit zjišťování zařízení.
- Povolte služby zjišťování polohy a stiskněte tlačítko Zpět.
- OK
- Odmítnout
- Přijmout
-
\ No newline at end of file
diff --git a/app/src/main/lib/sweetblue/library/src/main/res/values-da/strings.xml b/app/src/main/lib/sweetblue/library/src/main/res/values-da/strings.xml
deleted file mode 100755
index b4db934a..00000000
--- a/app/src/main/lib/sweetblue/library/src/main/res/values-da/strings.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
- Hvis du nægter adgang til placering, virker lavenergiscanning ikke.
- Appen har brug for android.permission.ACCESS_COARSE_LOCATION eller android.permission.ACCESS_FINE_LOCATION i dens AndroidManifest.xml!
- Klik på knappen Tilladelser, aktiver Placering, og tryk så to gange på tilbage.
- Android Marshmallow (6.0+) kræver placeringstilladelse for at kunne scanne for Bluetooth-enheder. Accepter for at give placeringstilladelse.
- Android Marshmallow (6.0+) kræver placeringstilladelse, for at appen kan scanne for Bluetooth-enheder.\n\nMarshmallow kræver også placeringsservices for at forbedre opdagelse af Bluetooth-enheder. Selvom det ikke kræves til brug i denne app, anbefales det for bedre at kunne opdage enheder.\n\nAccepter for at give placeringstilladelse og tillade placeringsservices.
- Android Marshmallow (6.0+) kræver placeringsservices for at forbedre scanning for Bluetooth-enheder. Det er ikke obligatorisk, men det anbefales at slå placeringsservices til for at forbedre opdagelse af enheder.
- Aktiver placeringsservices, og tryk så på tilbage.
- OK
- Nægt
- Accepter
-
\ No newline at end of file
diff --git a/app/src/main/lib/sweetblue/library/src/main/res/values-de/strings.xml b/app/src/main/lib/sweetblue/library/src/main/res/values-de/strings.xml
deleted file mode 100755
index 7e925238..00000000
--- a/app/src/main/lib/sweetblue/library/src/main/res/values-de/strings.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
- Wenn Sie den Zugriff auf Standortdienste verweigern, können mit BLE keine Geräte erkannt werden.
- Die App erfordert android.permission.ACCESS_COARSE_LOCATION oder android.permission.ACCESS_FINE_LOCATION in AndroidManifest.xml!
- Bitte klicken Sie auf den Button der Genehmigungen, erlauben Sie den Zugriff auf die Standortdienste und drücken Sie dann zweimal auf die Zurück-Taste.
- Bei Android Marshmallow (ab 6.0) ist der Zugriff auf Standortdienste erforderlich, um Bluetooth-Geräte zu finden. Bitte gewähren Sie den Zugriff.
- Bei Android Marshmallow (ab 6.0) ist der Zugriff auf Standortdienste erforderlich, um Bluetooth-Geräte zu finden.\n\nBei Marshmallow ist der Zugriff auf Standortdienste hilfreich, jedoch nicht zwingend für die Verwendung der App erforderlich. Er wird empfohlen, um Geräte besser zu erkennen.\n\nBitte gewähren Sie den Zugriff auf Standortdienste.
- Bei Android Marshmallow (ab 6.0) ist der Zugriff auf Standortdienste hilfreich, jedoch nicht zwingend für die Verwendung der App erforderlich. Er wird empfohlen, um Geräte über Bluetooth besser zu erkennen.
- Bitte gewähren Sie den Zugriff auf Standortdienste und drücken Sie dann auf die Zurück-Taste.
- OK
- Ablehnen
- Akzeptieren
-
\ No newline at end of file
diff --git a/app/src/main/lib/sweetblue/library/src/main/res/values-es/strings.xml b/app/src/main/lib/sweetblue/library/src/main/res/values-es/strings.xml
deleted file mode 100755
index d0d75f76..00000000
--- a/app/src/main/lib/sweetblue/library/src/main/res/values-es/strings.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
- Si se deniega el acceso a la localización el escaneo de baja energía no funcionará.
- La aplicación necesita android.permission.ACCESS_COARSE_LOCATION o android.permission.ACCESS_FINE_LOCATION en su AndroidManifest.xml.
- Haz clic en el botón Permisos, activa la Localización y luego pulsa atrás dos veces.
- Android Marshmallow (6.0+) necesita permiso de localización para poder escanear en busca de dispositivos Bluetooth. Acepta este mensaje para permitir el permiso de localización.
- Android Marshmallow (6.0+) necesita permiso de localización para la aplicación para que se pueda escanear en busca de dispositivos Bluetooth.\n\nMarshmallow también necesita los servicios de localización para mejorar la detección de dispositivos Bluetooth. A pesar de que su uso no es obligatorio en esta aplicación, se recomienda para mejorar la detección de dispositivos.\n\nAcepta este mensaje para permitir el permiso y los servicios de localización.
- Android Marshmallow (6.0+) necesita los servicios de localización para el escaneo de dispositivos Bluetooth de baja energía. Se recomienda encarecidamente activar los servicios de localización para mejorar la detección de dispositivos.
- Activa los servicios de localización y luego pulsa atrás.
- Aceptar
- Denegar
- Aceptar
-
\ No newline at end of file
diff --git a/app/src/main/lib/sweetblue/library/src/main/res/values-fr-rCA/strings.xml b/app/src/main/lib/sweetblue/library/src/main/res/values-fr-rCA/strings.xml
deleted file mode 100755
index b5b3930e..00000000
--- a/app/src/main/lib/sweetblue/library/src/main/res/values-fr-rCA/strings.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
- Refuser l\'accès de localisation signifie que le balayage à faible énergie ne fonctionnera pas.
- L\'application nécessite : android.permission.ACCESS_COARSE_LOCATION or android.permission.ACCESS_FINE_LOCATION in its AndroidManifest.xml!
- Veuillez appuyer sur la touche des permissions, ensuite activer la localisation, puis appuyer sur le retour deux fois.
- Android Marshmallow (6.0+) requiert la permission de localisation afin de pouvoir balayer pour des appareils Bluetooth. Veuillez accepter pour autoriser la permission de localisation.
- Android Marshmallow (6.0+) requiert la permission de localisation afin de pouvoir balayer pour des appareils Bluetooth.\n\nMarshmallow requiert aussi les services de localisation pour améliorer la découverte d\'appareils Bluetooth. Bien qu\'elle ne soit pas requise pour l\'utilisation de cette application, elle est recommandée afin de mieux découvrir des appareils.\n\nVeuillez accepter afin d\'autoriser la permission et les services de localisation.
- Android Marshmallow (6.0+) requiert les services de localisation pour le balayage amélioré d\'appareils Bluetooth. Il est recommandé que les services de localisation soient activés pour améliorer la découverte d\'appareils.
- Veuillez activer la localisation des services puis appuyez sur le retour.
- OK
- Refuser
- Accepter
-
\ No newline at end of file
diff --git a/app/src/main/lib/sweetblue/library/src/main/res/values-fr/strings.xml b/app/src/main/lib/sweetblue/library/src/main/res/values-fr/strings.xml
deleted file mode 100755
index b0555111..00000000
--- a/app/src/main/lib/sweetblue/library/src/main/res/values-fr/strings.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
- Refuser l\'accès à la position empêche le fonctionnement de l\'analyse \"énergie basse\".
- L\'application doit android.permission.ACCESS_COARSE_LOCATION ou android.permission.ACCESS_FINE_LOCATION dans son AndroidManifest.xml !
- Cliquez sur le bouton Autorisations, autorisez les services de localisation puis appuyez deux fois sur Retour.
- Android Marshmallow (6.0+) demande l\'Autorisation des Services de localisation pour pouvoir rechercher des appareils Bluetooth. Veuillez autoriser les services de localisation.
- Android Marshmallow (6.0+) demande l\'autorisation des Services de localisation pour que l\'application puisse détecter les appareils Bluetooth.\n\nMarshmallow demande également l\'activation des Services de localisation pour améliorer la détection d\'appareils Bluetooth. Si l\'activation des services de localisation n\'est pas obligatoire, elle est conseillée pour améliorer la détection d\'appareils.\n\nVeuillez autoriser les Services de localisation.
- Android Marshmallow (6.0+) demande l\'autorisation des Services de localisation pour rechercher des appareils Bluetooth avec plus d\'efficacité. Si l\'activation des services de localisation n\'est pas obligatoire, elle est conseillée pour améliorer la détection d\'appareils.
- Activez les services de localisation puis appuyez sur Retour.
- OK
- Refuser
- Accepter
-
\ No newline at end of file
diff --git a/app/src/main/lib/sweetblue/library/src/main/res/values-it/strings.xml b/app/src/main/lib/sweetblue/library/src/main/res/values-it/strings.xml
deleted file mode 100755
index cf3ccb1c..00000000
--- a/app/src/main/lib/sweetblue/library/src/main/res/values-it/strings.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
- Se rifiuti l\'accesso alla tua posizione, la scansione a basa potenza non funzionerà.
- L\'app necessita di android.permission.ACCESS_COARSE_LOCATION o android.permission.ACCESS_FINE_LOCATION in AndroidManifest.xml!
- Fare clic sul pulsante Autorizzazioni, abilitare Posizione e quindi premere due volte su Indietro.
- Android Marshmallow (6.0+) richiede l\'autorizzazione alla geolocalizzazione per eseguire la scansione dei dispositivi Bluetooth. Si prega di fornire tale autorizzazione.
- Android Marshmallow (6.0+) richiede l\'autorizzazione alla geolocalizzazione per eseguire la scansione dei dispositivi Bluetooth.\n\nMarshmallow richiede inoltre i servizi di localizzazione per migliorare la rilevazione dei dispositivi Bluetooth. Tale autorizzazione non è necessaria per l\'app, ma è consigliata per migliorare l\'individuazione dei dispositivi.\n\nSi prega di fornire l\'autorizzazione e di abilitare i servizi.
- Android Marshmallow (6.0+) richiede i servizi di localizzazione per migliorare la scansione dei dispositivi Bluetooth. È consigliabile, anche se non obbligatorio, abilitare questi servizi per migliorare la rilevazione dei dispositivi.
- Abilitare i servizi di localizzazione e quindi premere Indietro
- OK
- Rifiuta
- Accetta
-
\ No newline at end of file
diff --git a/app/src/main/lib/sweetblue/library/src/main/res/values-ja/strings.xml b/app/src/main/lib/sweetblue/library/src/main/res/values-ja/strings.xml
deleted file mode 100755
index 45e0a75b..00000000
--- a/app/src/main/lib/sweetblue/library/src/main/res/values-ja/strings.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
- 位置情報へのアクセスを許可しない場合、省エネルギーのスキャン機能はご利用いただけません。
- android.permission.ACCESS_COARSE_LOCATIONまたはandroid.permission.ACCESS_FINE_LOCATIONがアプリのAndroidManifest.xmlに含まれている必要があります。
- パーミッションボタンをクリックしてから位置情報をオンにし、2回押して戻ります。
- Android Marshmallow (6.0+)では、Bluetooth端末をスキャンする際に位置情報のパーミッション(許可)が必要となります。必ず許可するようにしてください。
- Android Marshmallow (6.0+)では、アプリ内でBluetooth端末をスキャンする際に位置情報のパーミッション(許可)が必要となります。\n\nMarshmallowではさらに、Bluetooth端末をより検出しやすくするため、位置情報サービスの使用が求められます。位置情報サービスはアプリ内では必須ではありませんが、端末を検出しやすくするため使用が推奨されています。\n\n位置情報のパーミッションとサービスを許可するようにしてください。
- Android Marshmallow (6.0+)では、Bluetooth省エネ端末のスキャンの向上のため、位置情報サービスが必要となります。端末の検出をより行いやすくするため、位置情報サービスをオンにすることを強くお勧めします。
- 位置情報サービスをオンにしてから戻ってください。
- OK
- 拒否
- 許可
-
\ No newline at end of file
diff --git a/app/src/main/lib/sweetblue/library/src/main/res/values-ko/strings.xml b/app/src/main/lib/sweetblue/library/src/main/res/values-ko/strings.xml
deleted file mode 100755
index 7a368c3c..00000000
--- a/app/src/main/lib/sweetblue/library/src/main/res/values-ko/strings.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
- 위치 서비스를 사용하지 않으면 저전력 스캔이 불가능합니다.
- 앱의 AndroidManifest.xml 안에 android.permission.ACCESS_COARSE_LOCATION 또는 android.permission.ACCESS_FINE_LOCATION이 필요합니다!
- 허용 버튼을 클릭하고 위치 서비스를 켠 후 뒤로 가기를 두 번 누르세요.
- Android Marshmallow(6.0+)가 블루투스 기기를 스캔하려면 위치 서비스를 허용해야 합니다. 위치 서비스 허용을 선택해주세요.
- Android Marshmallow(6.0+)가 블루투스 기기를 스캔하려면 위치 서비스를 허용해야 합니다.\n\n또한 Marshmallow가 블루투스 기기 스캔 성능을 향상시키려면 위치 서비스가 필요합니다. 이 앱에서 반드시 필요하지는 않으나 스캔 성능 향상에 권장합니다.\n\n위치 서비스 허용 및 서비스 사용을 선택해주세요.
- Android Marshmallow(6.0+)가 블루투스 기기를 보다 정확히 스캔하려면 위치 서비스를 허용해야 합니다. 이 앱에서 반드시 필요하지는 않으나 스캔 성능 향상에 권장합니다.
- 위치 서비스를 켠 후 뒤로 가기를 누르세요.
- 확인
- 허용 안함
- 허용
-
\ No newline at end of file
diff --git a/app/src/main/lib/sweetblue/library/src/main/res/values-nl/strings.xml b/app/src/main/lib/sweetblue/library/src/main/res/values-nl/strings.xml
deleted file mode 100755
index 13c6b4dd..00000000
--- a/app/src/main/lib/sweetblue/library/src/main/res/values-nl/strings.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
- Zonder locatietoegang werkt Low Energy-scannen niet.
- App vereist android.permission.ACCESS_COARSE_LOCATION of android.permission.ACCESS_FINE_LOCATION in AndroidManifest.xml!
- Klik op de knop Toestemmingen, schakel Locatie in en druk vervolgens tweemaal op terug.
- Android Marshmallow (6.0+) vereist Locatietoestemming om te scannen naar Bluetooth-apparaten. Accepteer om Locatietoestemming toe te staan.
- Android Marshmallow (6.0+) vereist Locatietoestemming voor de app om te scannen naar Bluetooth-apparaten.\n\nMarshmallow vereist ook Locatieservices om het detecteren van Bluetooth-apparaten te verbeteren. Het is niet vereist voor gebruik in de app, het wordt wel aanbevolen om apparaten beter te detecteren.\n\nAccepteer om Locatietoestemming- en services toe te staan.
- Android Marshmallow (6.0+) vereist Locatieservices om beter Bluetooth-apparaten te kunnen detecteren. We raden de inschakeling van Locatieservices aan om apparaatdetectie te verbeteren.
- Schakel Locatieservices in en druk op terug.
- OK
- Afwijzen
- Accepteren
-
\ No newline at end of file
diff --git a/app/src/main/lib/sweetblue/library/src/main/res/values-nn/strings.xml b/app/src/main/lib/sweetblue/library/src/main/res/values-nn/strings.xml
deleted file mode 100755
index f6cb22ff..00000000
--- a/app/src/main/lib/sweetblue/library/src/main/res/values-nn/strings.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
- Hvis du avslår stedstjenester, vil du ikke kunne bruke enhetsoppdagelse på lav energi.
- Appen trenger android.permission.ACCESS_COARSE_LOCATION eller android.permission.ACCESS_FINE_LOCATION i AndroidManifest.xml!
- Vennligst klikk på knappen Tillatelser, deretter aktiver Sted og trykk tilbake to ganger.
- Android Marshmallow (6.0+) krever Stedstjenester for å kunne se etter andre Bluetooth-enheter. Vennligst godta å bruke Stedstjenester.
- Android Marshmallow (6.0+) krever Stedstillatelse for å kunne se etter andre Bluetooth-enheter.\n\nMarshmallow krever også Stedstjenester for å forbedre Bluetooth-scanning. Mens det imidlertid ikke er påkrevd for å bruke denne applikasjonen, er det anbefalt for bedre oppdagelse av andre enheter.\n\nVennligst godta å bruke Stedstjenester.
- Android Marshmallow (6.0+) krever Stedstjenester for forbedret Bluetooth-scanning. Mens det imidlertid ikke er påkrevd, er det anbefalt å slå denne innstillingen på for å forbedre enhetsoppdagelse.
- Vennligst tillat Stedstjenester og så trykk tilbake.
- OK
- Avslå
- Godta
-
\ No newline at end of file
diff --git a/app/src/main/lib/sweetblue/library/src/main/res/values-pl/strings.xml b/app/src/main/lib/sweetblue/library/src/main/res/values-pl/strings.xml
deleted file mode 100755
index 59649aa8..00000000
--- a/app/src/main/lib/sweetblue/library/src/main/res/values-pl/strings.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
- Odrzucenie dostępu do lokalizacji spowoduje brak możliwości skanowania z ograniczonym poborem energii.
- Aplikacja potrzebuje android.permission.ACCESS_COARSE_LOCATION or android.permission.ACCESS_FINE_LOCATION in its AndroidManifest.xml!
- Proszę nacisnąć przycisk Pozwolenia, po czym włączyć opcję Lokalizacja i nacisnąć Wróć dwa razy.
- Android Marshmallow (6.0+) potrzebuje zgody na dostęp do lokalizacji, aby móc skanować urządzenia Bluetooth. Proszę wyrazić zgodę na dostęp do lokalizacji.
- Android Marshmallow (6.0+) potrzebuje zgody na dostęp do lokalizacji, aby móc skanować urządzenia Bluetooth.\n\nMarshmallow również potrzebuje dostępu do usług lokalizacyjnych, aby zapewnić lepsze wyszukiwanie urządzeń Bluetooth. Wyrażenie zgody na dostęp do lokalizacji nie jest wymagane do korzystania z tej aplikacji, jednak zalecane, aby zapewnić lepsze wyszukiwanie urządzeń.\n\nProszę wyrazić zgodę na dostęp do lokalizacji i na usługi lokalizacyjne.
- Android Marshmallow (6.0+) potrzebuje zgody na dostęp do lokalizacji, aby zapewnić lepsze skanowanie urządzeń Bluetooth. Wyrażenie zgody na dostęp do lokalizacji nie jest wymagane, jednak zaleca się włączenie usług lokalizacyjnych, aby zapewnić lepsze wyszukiwanie urządzeń.
- Proszę włączyć Usługi lokalizacyjne, po czym nacisnąć \"Wróć\" dwa razy.
- OK
- Odmów
- Akceptuj
-
\ No newline at end of file
diff --git a/app/src/main/lib/sweetblue/library/src/main/res/values-pt-rBR/strings.xml b/app/src/main/lib/sweetblue/library/src/main/res/values-pt-rBR/strings.xml
deleted file mode 100755
index 117b9197..00000000
--- a/app/src/main/lib/sweetblue/library/src/main/res/values-pt-rBR/strings.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
- Recusar o acesso à localização fará com que o escaneamento com pouca energia não funcione.
- O aplicativo precisa de android.permission.ACCESS_COARSE_LOCATION ou android.permission.ACCESS_FINE_LOCATION iem seu AndroidManifest.xml.
- Clique no botão Permissões, ative a Localização e pressione Voltar duas vezes.
- O Android Marshmallow (6.0+) requer a Permissão de localização para poder escanear dispositivos Bluetooth. Aceite para ativar a Permissão de localização.
- O Android Marshmallow (6.0+) requer a Permissão de localização para poder escanear dispositivos Bluetooth.\n\nO Marshmallow também requer Serviços de localização para melhorar a detecção de dispositivos Bluetooth. Embora não seja necessário para usar neste aplicativo, é recomendado para detectar melhor os dispositivos.\n\nAceite para ativar Permissão e serviços de localização.
- O Android Marshmallow (6.0+) requer Serviços de localização para um melhor escaneamento de dispositivos Bluetooth. Embora não seja necessário, é recomendado que os Serviços de localização estejam ativados para uma melhor detecção de dispositivos.
- Ative Serviços de localização e pressione Voltar.
- OK
- Recusar
- Aceitar
-
\ No newline at end of file
diff --git a/app/src/main/lib/sweetblue/library/src/main/res/values-ru/strings.xml b/app/src/main/lib/sweetblue/library/src/main/res/values-ru/strings.xml
deleted file mode 100755
index 7c1fe510..00000000
--- a/app/src/main/lib/sweetblue/library/src/main/res/values-ru/strings.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
- Если запретить доступ к геоданным, BLE-сканирование перестанет работать.
- Для приложения нужно указать android.permission.ACCESS_COARSE_LOCATION или android.permission.ACCESS_FINE_LOCATION в файле AndroidManifest.xml!
- Нажмите кнопку «Разрешения», включите определение местоположения, а затем дважды нажмите «Назад».
- Чтобы можно было сканировать устройства с Bluetooth, системе Android Marshmallow (6.0 и более поздней версии) требуется доступ к геоданным. Нажмите «Принять», чтобы разрешить доступ.
- Чтобы приложение сканировало устройства с Bluetooth, системе Android Marshmallow (6.0 и более поздней версии) требуется доступ к геоданным.\n\nТакже для улучшенного обнаружения устройств с Bluetooth системе нужно определение местоположения. Когда оно не требуется в приложении, рекомендуем его использовать.\n\nНажмите «Принять», чтобы разрешить доступ к геоданным и включить определение местоположения.
- Для улучшенного сканирования устройств с Bluetooth системе Android Marshmallow (6.0 и более поздней версии) нужно определение местоположения. Когда эта функция не требуется, рекомендуем включить ее, чтобы улучшить обнаружение устройств.
- Включите определение местоположения и нажмите «Назад».
- ОК
- Отклонить
- Принять
-
\ No newline at end of file
diff --git a/app/src/main/lib/sweetblue/library/src/main/res/values-sv/strings.xml b/app/src/main/lib/sweetblue/library/src/main/res/values-sv/strings.xml
deleted file mode 100755
index 72844c69..00000000
--- a/app/src/main/lib/sweetblue/library/src/main/res/values-sv/strings.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
- Om du nekar tillgång till platstjänster fungerar inte low energy-skanning.
- Appen behöver android.permission.ACCESS_COARSE_LOCATION eller android.permission.ACCESS_FINE_LOCATION i sitt AndroidManifest.xml!
- Vänligen tryck på Behörigheter-knappen, aktivera Plats och tryck sedan på Tillbaka två gånger.
- Android Marshmallow (6.0+) kräver Platsåtkomst för att kunna skanna efter Bluetooth-enheter. Vänligen acceptera för att tillåta Platsåtkomst.
- Android Marshmallow (6.0+) kräver Platsåtkomst för att appen ska kunna skanna efter Bluetooth-enhterer.\n\nMarshmallow behöver Platstjänster för att förbättra upptäckt av Bluetooth-enheter. Det är inget krav för att appen ska fungea, men rekommenderas för att bättre upptäcka andra enheter.\n\nVänligen acceptera för att tillåta Platsåtkomst och Platstjänster.
- Android Marshmallow (6.0+) använder Platstjänster för att förbättra skanning efter Bluetooth-enheter. Det är inget krav för att appen ska fungera, men det rekommenderas att Platstjänster är aktiverat för att bättre upptäcka andra enheter.
- Vänligen aktivera Platstjänster och knacka därefter på tillbaka.
- OK
- Neka
- Godkänn
-
\ No newline at end of file
diff --git a/app/src/main/lib/sweetblue/library/src/main/res/values-zh-rCN/strings.xml b/app/src/main/lib/sweetblue/library/src/main/res/values-zh-rCN/strings.xml
deleted file mode 100755
index c05b3a44..00000000
--- a/app/src/main/lib/sweetblue/library/src/main/res/values-zh-rCN/strings.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
- 如果拒絕位置資訊存取權限,則無法使用低功耗掃描功能。
- 此應用程式的 AndroidManifest.xml 中必須有 android.permission.ACCESS_COARSE_LOCATION 或 android.permission.ACCESS_FINE_LOCATION!
- 請按一下 [權限] 按鈕,然後啟用定位功能,最後按返回兩次。
- Android Marshmallow (6.0+) 需要位置權限才能搜尋藍牙裝置。請按下 [接受] 以便授予位置權限。
- Android Marshmallow (6.0+) 需要應用程式的位置權限才能搜尋藍牙裝置。\n\nMarshmallow 也需要定位服務才能提高藍牙裝置搜尋能力。雖然這並非應用程式的必要選項,但還是建議您啟用這項功能,以便提高裝置搜尋能力。\n\n請按下 [接受] 以便授予位置權限和定位服務權限。
- Android Marshmallow (6.0+) 需要定位服務才能提高藍牙裝置掃描能力。雖然這並非必要選項,但還是建議您啟用定位服務,以便提高裝置搜尋能力。
- 請啟用定位服務,然後按返回。
- 好
- 拒絕
- 接受
-
\ No newline at end of file
diff --git a/app/src/main/lib/sweetblue/library/src/main/res/values-zh-rTW/strings.xml b/app/src/main/lib/sweetblue/library/src/main/res/values-zh-rTW/strings.xml
deleted file mode 100755
index 3192154e..00000000
--- a/app/src/main/lib/sweetblue/library/src/main/res/values-zh-rTW/strings.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
- 拒绝位置服务,意味着将不能进行低能量扫描。
- 本应用需要在其 AndroidManifest.xml 文件中有 android.permission.ACCESS_COARSE_LOCATION 或 android.permission.ACCESS_FINE_LOCATION!
- 请点按”权限“按钮,启用”定位“,然后按两次后退。
- Android Marshmallow (6.0+) 需要定位权限才能扫描蓝牙设备。请接受以允许定位权限。
- Android Marshmallow (6.0+) 需要定位权限才能扫描蓝牙设备。\n\nMarshmallow 也需要定位服务,才能改进蓝牙设备发现。尽管本应用不需要,但建议您将定位服务打开,以获得更好的设备发现能力。\n\n请接受,以允许使用定位权限和定位服务。
- Android Marshmallow (6.0+) 需要定位服务才能使用改进的蓝牙设备扫描功能。尽管不需要,但建议将定位服务打开,以提高设备发现能力。
- 请启用定位服务,然后按后退。
- 好
- 拒绝
- 接受
-
\ No newline at end of file
diff --git a/app/src/main/lib/sweetblue/library/src/main/res/values/strings.xml b/app/src/main/lib/sweetblue/library/src/main/res/values/strings.xml
deleted file mode 100644
index 2d227b17..00000000
--- a/app/src/main/lib/sweetblue/library/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
- Denying location access means low-energy scanning will not work.
- App needs android.permission.ACCESS_COARSE_LOCATION or android.permission.ACCESS_FINE_LOCATION in its AndroidManifest.xml!
- Please click the Permissions button, then enable Location, then press back twice.
- Android Marshmallow (6.0+) requires Location Permission to be able to scan for Bluetooth devices. Please accept to allow Location Permission.
- Android Marshmallow (6.0+) requires Location Permission to the app to be able to scan for Bluetooth devices.
-
-Marshmallow also requires Location Services to improve Bluetooth device discovery. While it is not required for use in this app, it is recommended to better discover devices.
-
-Please accept to allow Location Permission and Services.
- Android Marshmallow (6.0+) requires Location Services for improved Bluetooth device scanning. While it is not required, it is recommended that Location Services are turned on to improve device discovery.
- Please enable Location Services then press back.
- OK
- Deny
- Accept
-
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 738d9f60..ce201a57 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,18 +1,3 @@
-buildscript {
- repositories {
- mavenCentral()
- maven("https://maven.google.com")
- google()
- }
- dependencies {
- classpath("com.android.tools.build:gradle:7.4.2")
- classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21")
- }
-}
-
-allprojects {
- repositories {
- mavenCentral()
- google()
- }
+plugins {
+ alias(libs.plugins.android.application) apply false
}
diff --git a/gradle.properties b/gradle.properties
index 9a2223a5..b454ce82 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,3 +1,6 @@
-android.enableJetifier=true
android.useAndroidX=true
+android.nonTransitiveRClass=true
org.gradle.jvmargs=-Xmx4096m
+org.gradle.caching=true
+org.gradle.parallel=true
+org.gradle.configuration-cache=true
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100644
index 00000000..aa2d55bc
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,32 @@
+[versions]
+agp = "8.5.2"
+appcompat = "1.7.0"
+cardview = "1.0.0"
+material = "1.12.0"
+retrofit = "2.11.0"
+okhttpLogging = "4.12.0"
+gson = "2.11.0"
+osmdroid = "6.1.20"
+# Nordic BLE libraries are intentionally pinned: the connection lifecycle was
+# carefully tuned against these versions.
+nordicScanner = "1.6.0"
+nordicBle = "2.7.2"
+junit = "4.13.2"
+
+[libraries]
+androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
+androidx-cardview = { group = "androidx.cardview", name = "cardview", version.ref = "cardview" }
+material = { group = "com.google.android.material", name = "material", version.ref = "material" }
+# Runtime dependencies of the vendored EasyWeather library
+# (see app/src/main/lib/easyweather/README.md).
+retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
+retrofit-converter-gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit" }
+okhttp-logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttpLogging" }
+gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
+osmdroid-android = { group = "org.osmdroid", name = "osmdroid-android", version.ref = "osmdroid" }
+nordic-scanner = { group = "no.nordicsemi.android.support.v18", name = "scanner", version.ref = "nordicScanner" }
+nordic-ble = { group = "no.nordicsemi.android", name = "ble", version.ref = "nordicBle" }
+junit = { group = "junit", name = "junit", version.ref = "junit" }
+
+[plugins]
+android-application = { id = "com.android.application", version.ref = "agp" }
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index d6c35de1..45181329 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,5 @@
-#Sun Dec 06 09:32:39 CET 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 15a801b1..f9352457 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1 +1,18 @@
+pluginManagement {
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+rootProject.name = "AsteroidOSSync"
include(":app")