diff --git a/app/src/main/java/com/wytehat/btlogger/BluetoothTrackingService.java b/app/src/main/java/com/wytehat/btlogger/BluetoothTrackingService.java index e3408dc..9c7b26b 100644 --- a/app/src/main/java/com/wytehat/btlogger/BluetoothTrackingService.java +++ b/app/src/main/java/com/wytehat/btlogger/BluetoothTrackingService.java @@ -25,10 +25,6 @@ public class BluetoothTrackingService extends Service implements LocationListene public static final String ACTION_DATA_CHANGED = "com.wytehat.btlogger.DATA_CHANGED"; public static final String ACTION_CONFIG_CHANGED = "com.wytehat.btlogger.CONFIG_CHANGED"; public static final String ACTION_RANGE_MODE = "com.wytehat.btlogger.RANGE_MODE"; - public static final String ACTION_ACK_LEFT_BEHIND = "com.wytehat.btlogger.ACK_LEFT_BEHIND"; - public static final String EXTRA_ADDRESS = "address"; - /** How often an unacknowledged left-behind item re-alerts. */ - private static final long ALERT_REPEAT_MS = 60000L; private static final int NOTIFICATION_ID = 42; private static final String CHANNEL_ID = "bluetooth_tracking"; private static final String ALERT_CHANNEL_ID = "tracked_item_disconnects"; @@ -41,9 +37,6 @@ public class BluetoothTrackingService extends Service implements LocationListene private int phoneBattery = 100; private final Handler handler = new Handler(); private final HashMap lastPeriodicLog = new HashMap(); - /** Items reported left behind, address -> display name, until acknowledged. */ - private final HashMap leftBehind = new HashMap(); - private boolean alertLoopRunning; private final BroadcastReceiver receiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { @@ -52,10 +45,6 @@ public class BluetoothTrackingService extends Service implements LocationListene rangeModeActive = intent.getBooleanExtra("active", false); return; } - if (ACTION_ACK_LEFT_BEHIND.equals(action)) { - acknowledgeLeftBehind(intent.getStringExtra(EXTRA_ADDRESS)); - return; - } if (ACTION_CONFIG_CHANGED.equals(action)) { restartBackgroundScan(); configureLocation(); @@ -95,7 +84,7 @@ public class BluetoothTrackingService extends Service implements LocationListene DeviceRecord previous = db.get(address); db.disconnected(address, broadcastName, now, fix, infer(previous)); if (previous != null && previous.tracked == 1 && previous.connected == 1) - markLeftBehind(previous, infer(previous)); + showDisconnectAlert(previous, infer(previous)); captureFresh(address, now); configureLocation(); } else if (BluetoothDevice.ACTION_FOUND.equals(action)) { @@ -186,7 +175,6 @@ public class BluetoothTrackingService extends Service implements LocationListene filter.addAction(Intent.ACTION_BATTERY_CHANGED); filter.addAction(ACTION_CONFIG_CHANGED); filter.addAction(ACTION_RANGE_MODE); - filter.addAction(ACTION_ACK_LEFT_BEHIND); registerReceiver(receiver, filter); receiverRegistered = true; locationManager = (LocationManager) getSystemService(LOCATION_SERVICE); @@ -204,7 +192,6 @@ public class BluetoothTrackingService extends Service implements LocationListene public void onDestroy() { handler.removeCallbacks(scanTask); - handler.removeCallbacks(alertTask); try { if (bluetooth != null) bluetooth.stopLeScan(leScanCallback); } catch (Exception ignored) { } if (receiverRegistered) unregisterReceiver(receiver); @@ -431,99 +418,16 @@ public class BluetoothTrackingService extends Service implements LocationListene return first.getTime() >= second.getTime() ? first : second; } - /** - * FLAG_IMMUTABLE (API 23+) is referenced by value because this module - * still compiles against SDK 21. - */ - private int pendingFlags() { - int flags = PendingIntent.FLAG_UPDATE_CURRENT; - if (Build.VERSION.SDK_INT >= 23) flags |= 0x04000000; - return flags; - } - - private int alertId(String address) { - return 1000 + Math.abs(address.hashCode() % 100000); - } - - private PendingIntent acknowledgeIntent(String address) { - Intent ack = new Intent(ACTION_ACK_LEFT_BEHIND).setPackage(getPackageName()); - if (address != null) ack.putExtra(EXTRA_ADDRESS, address); - return PendingIntent.getBroadcast(this, - address == null ? 0 : alertId(address), ack, pendingFlags()); - } - - /** - * Records the item as left behind and keeps alerting until the user - * taps "Acknowledge Left Behind" - a swipe alone does not clear it. - */ - private void markLeftBehind(DeviceRecord record, String reason) { - if (record == null || record.address == null) return; - leftBehind.put(record.address, record.displayName()); - showDisconnectAlert(record, reason); - updateForegroundNotification(); - startAlertLoop(); - } - - private void acknowledgeLeftBehind(String address) { - NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); - if (address == null) { - for (String pending : leftBehind.keySet().toArray(new String[0])) { - try { manager.cancel(alertId(pending)); } catch (Exception ignored) { } - } - leftBehind.clear(); - } else { - leftBehind.remove(address); - try { manager.cancel(alertId(address)); } catch (Exception ignored) { } - } - if (leftBehind.isEmpty()) { - handler.removeCallbacks(alertTask); - alertLoopRunning = false; - } - updateForegroundNotification(); - } - - private void startAlertLoop() { - if (alertLoopRunning) return; - alertLoopRunning = true; - handler.postDelayed(alertTask, ALERT_REPEAT_MS); - } - - /** Re-fires every outstanding alert until each one is acknowledged. */ - private final Runnable alertTask = new Runnable() { - public void run() { - if (leftBehind.isEmpty()) { alertLoopRunning = false; return; } - for (String address : leftBehind.keySet().toArray(new String[0])) { - DeviceRecord record = db.get(address); - if (record == null) continue; - showDisconnectAlert(record, "Still left behind - not acknowledged"); - } - updateForegroundNotification(); - handler.postDelayed(this, ALERT_REPEAT_MS); - } - }; - - private void updateForegroundNotification() { - try { - NotificationManager manager = - (NotificationManager) getSystemService(NOTIFICATION_SERVICE); - manager.notify(NOTIFICATION_ID, buildNotification()); - } catch (Exception ignored) { } - } - private void showDisconnectAlert(DeviceRecord record, String reason) { Intent open = new Intent(this, MainActivity.class); PendingIntent pending = PendingIntent.getActivity(this, record.address.hashCode(), open, - pendingFlags()); + PendingIntent.FLAG_UPDATE_CURRENT); Notification.Builder builder = new Notification.Builder(this) .setSmallIcon(R.drawable.ic_launcher) - .setContentTitle(record.displayName() + " left behind") + .setContentTitle(record.displayName() + " disconnected") .setContentText(reason == null ? "Your tracked item is no longer connected" : reason) - .setContentIntent(pending).setAutoCancel(false).setOngoing(true) + .setContentIntent(pending).setAutoCancel(true) .setDefaults(Notification.DEFAULT_ALL).setPriority(Notification.PRIORITY_HIGH); - if (Build.VERSION.SDK_INT >= 16) { - builder.addAction(R.drawable.ic_launcher, "Acknowledge Left Behind", - acknowledgeIntent(record.address)); - } NotificationManager manager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); if (Build.VERSION.SDK_INT >= 26) { try { @@ -541,24 +445,12 @@ public class BluetoothTrackingService extends Service implements LocationListene private Notification buildNotification() { Intent open = new Intent(this, MainActivity.class); - PendingIntent pending = PendingIntent.getActivity(this, 0, open, pendingFlags()); - int outstanding = leftBehind.size(); - String title = outstanding > 0 - ? outstanding + (outstanding == 1 ? " item left behind" : " items left behind") - : "Bluetooth Item Finder"; - String text = outstanding > 0 - ? "Alerting until acknowledged: " + describeLeftBehind() - : "Watching your tracked items"; + PendingIntent pending = PendingIntent.getActivity(this, 0, open, + PendingIntent.FLAG_UPDATE_CURRENT); Notification.Builder builder = new Notification.Builder(this) - .setSmallIcon(R.drawable.ic_launcher).setContentTitle(title) - .setContentText(text).setOngoing(true) + .setSmallIcon(R.drawable.ic_launcher).setContentTitle("Bluetooth Item Finder") + .setContentText("Watching your tracked items").setOngoing(true) .setContentIntent(pending); - if (Build.VERSION.SDK_INT >= 16 && outstanding > 0) { - builder.addAction(R.drawable.ic_launcher, - outstanding == 1 ? "Acknowledge Left Behind" : "Acknowledge All", - acknowledgeIntent(outstanding == 1 - ? leftBehind.keySet().iterator().next() : null)); - } if (Build.VERSION.SDK_INT >= 26) { try { Class channelClass = Class.forName("android.app.NotificationChannel"); @@ -575,14 +467,5 @@ public class BluetoothTrackingService extends Service implements LocationListene } return builder.build(); } - - private String describeLeftBehind() { - StringBuilder names = new StringBuilder(); - for (String name : leftBehind.values()) { - if (names.length() > 0) names.append(", "); - names.append(name); - } - return names.toString(); - } } diff --git a/app/src/main/java/com/wytehat/btlogger/DeviceSort.java b/app/src/main/java/com/wytehat/btlogger/DeviceSort.java deleted file mode 100644 index f48162d..0000000 --- a/app/src/main/java/com/wytehat/btlogger/DeviceSort.java +++ /dev/null @@ -1,121 +0,0 @@ -package com.wytehat.btlogger; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -/** - * Ordering for the tracked item list. - * - * Default order is newest sighting first. Items that have not been seen for - * a long time - or that carry no sighting timestamp at all - are pushed to - * the bottom and stay there in both sort directions, so the reverse toggle - * never buries a live item under a pile of stale ones. - */ -final class DeviceSort { - - /** A device unseen for longer than this sinks to the bottom. */ - static final long STALE_AFTER_MS = 24L * 60L * 60L * 1000L; - - private DeviceSort() { - } - - /** - * Last moment the device was actually observed. Deliberately ignores - * updatedAt, which also moves when the user edits a name or colour. - */ - static long lastSeen(DeviceRecord record) { - - if (record == null) { - return 0L; - } - - long seen = record.lastRssiAt; - - if (record.connectedAt > seen) { - seen = record.connectedAt; - } - - if (record.disconnectedAt > seen) { - seen = record.disconnectedAt; - } - - return seen; - } - - static boolean isStale(DeviceRecord record, long now) { - - long seen = lastSeen(record); - - return seen <= 0L || - now - seen > STALE_AFTER_MS; - } - - /** - * @param reverse false = newest first (default), true = oldest first. - */ - static List sorted(List rows, boolean reverse) { - - ArrayList fresh = new ArrayList(); - ArrayList stale = new ArrayList(); - - if (rows == null) { - return fresh; - } - - long now = System.currentTimeMillis(); - - for (int i = 0; i < rows.size(); i++) { - - DeviceRecord record = rows.get(i); - - if (record == null) { - continue; - } - - if (isStale(record, now)) { - stale.add(record); - } else { - fresh.add(record); - } - } - - Comparator newestFirst = - new Comparator() { - - public int compare(DeviceRecord left, DeviceRecord right) { - - long a = lastSeen(left); - long b = lastSeen(right); - - if (a == b) { - return left.displayName() - .compareToIgnoreCase(right.displayName()); - } - - return a > b ? -1 : 1; - } - }; - - Collections.sort(fresh, newestFirst); - Collections.sort(stale, newestFirst); - - if (reverse) { - Collections.reverse(fresh); - Collections.reverse(stale); - } - - ArrayList result = - new ArrayList(fresh.size() + stale.size()); - - result.addAll(fresh); - - /* - * Stale entries are appended last in both directions. - */ - result.addAll(stale); - - return result; - } -} diff --git a/app/src/main/java/com/wytehat/btlogger/MainActivity.java b/app/src/main/java/com/wytehat/btlogger/MainActivity.java index 7eebb2a..e254c03 100644 --- a/app/src/main/java/com/wytehat/btlogger/MainActivity.java +++ b/app/src/main/java/com/wytehat/btlogger/MainActivity.java @@ -17,7 +17,6 @@ public class MainActivity extends Activity implements ItemActionListener { private static final int CAMERA = 200; private TrackerDatabase db; private DeviceListAdapter adapter; - private boolean reverseSort; private boolean registered; private DeviceRecord editing; private final Handler refreshHandler = new Handler(); @@ -47,18 +46,6 @@ public class MainActivity extends Activity implements ItemActionListener { public void onClick(View v) { startActivity(new Intent(MainActivity.this, MapActivity.class)); } }); - reverseSort = getSharedPreferences("list_settings", 0) - .getBoolean("reverse_sort", false); - updateSortButton(); - findViewById(R.id.sort_button).setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - reverseSort = !reverseSort; - getSharedPreferences("list_settings", 0).edit() - .putBoolean("reverse_sort", reverseSort).apply(); - updateSortButton(); - reload(); - } - }); findViewById(R.id.settings_button).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { showSettings(); } }); @@ -360,21 +347,7 @@ public class MainActivity extends Activity implements ItemActionListener { PackageManager.PERMISSION_GRANTED) list.add(permission); } - private void reload() { - if (adapter == null) return; - adapter.setRows(DeviceSort.sorted(db.tracked(), reverseSort)); - } - - /* - * Stale items stay pinned to the bottom in both directions, so the - * label only describes how the recently seen items are ordered. - */ - private void updateSortButton() { - android.widget.Button button = - (android.widget.Button) findViewById(R.id.sort_button); - if (button == null) return; - button.setText(reverseSort ? "Oldest first" : "Newest first"); - } + private void reload() { if (adapter != null) adapter.setRows(db.tracked()); } private void startTracker() { Intent intent = new Intent(this, BluetoothTrackingService.class); diff --git a/app/src/main/java/com/wytehat/btlogger/MapActivity.java b/app/src/main/java/com/wytehat/btlogger/MapActivity.java index 37f3d90..55be9d6 100644 --- a/app/src/main/java/com/wytehat/btlogger/MapActivity.java +++ b/app/src/main/java/com/wytehat/btlogger/MapActivity.java @@ -26,7 +26,6 @@ import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.CheckBox; import android.widget.LinearLayout; -import android.widget.ScrollView; import android.widget.Spinner; import android.widget.TextView; @@ -55,8 +54,6 @@ implements SensorEventListener { private LinearLayout filterPanel; - - private ScrollView filterScroll; private Spinner categorySpinner; private Spinner deviceSpinner; private Spinner resolutionSpinner; @@ -256,6 +253,8 @@ implements SensorEventListener { dp(4) ); + filterPanel.setVisibility(View.GONE); + /* * Category. */ @@ -322,27 +321,11 @@ implements SensorEventListener { /* * Trail resolution (downsampling). */ - LinearLayout resolutionRow = - new LinearLayout(this); - - resolutionRow.setOrientation( - LinearLayout.HORIZONTAL - ); - - resolutionRow.setGravity( - android.view.Gravity.CENTER_VERTICAL - ); - TextView resolutionLabel = smallLabel("Trail detail"); - resolutionRow.addView( - resolutionLabel, - new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.WRAP_CONTENT - ) - ); + filterPanel.addView(resolutionLabel); + resolutionSpinner = new Spinner(this); for (int i = 0; @@ -369,16 +352,11 @@ implements SensorEventListener { resolutionAdapter ); - resolutionRow.addView( + filterPanel.addView( resolutionSpinner, - new LinearLayout.LayoutParams( - 0, - dp(38), - 1 - ) + compactParams() ); - filterPanel.addView(resolutionRow); /* * Time. */ @@ -494,30 +472,14 @@ implements SensorEventListener { filterPanel.addView(showAll); - /* - * The panel is scrollable and bounded to a third of the - * screen so every control stays reachable on short screens. - */ - filterScroll = new ScrollView(this); + root.addView( + filterPanel, + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ) + ); - filterScroll.setVisibility(View.GONE); - - filterScroll.addView( - filterPanel, - new android.widget.FrameLayout.LayoutParams( - android.widget.FrameLayout.LayoutParams.MATCH_PARENT, - android.widget.FrameLayout.LayoutParams.WRAP_CONTENT - ) - ); - - root.addView( - filterScroll, - new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - 0, - 1 - ) - ); /* * WebView map. */ @@ -559,13 +521,13 @@ implements SensorEventListener { } ); - root.addView( - web, - new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - 0, - 2 - ) + root.addView( + web, + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + 0, + 1 + ) ); setContentView(root); @@ -599,12 +561,19 @@ implements SensorEventListener { @Override public void onClick(View view) { - filterScroll.setVisibility( - filterScroll.getVisibility() - == View.VISIBLE - ? View.GONE - : View.VISIBLE - ); + if (filterPanel.getVisibility() + == View.VISIBLE) { + + filterPanel.setVisibility( + View.GONE + ); + + } else { + + filterPanel.setVisibility( + View.VISIBLE + ); + } } } ); @@ -806,8 +775,8 @@ implements SensorEventListener { /* * Collapse after applying. */ - filterScroll.setVisibility( - View.GONE + filterPanel.setVisibility( + View.GONE ); } } diff --git a/app/src/main/res/layout/main.xml b/app/src/main/res/layout/main.xml index acb3b40..015ed02 100644 --- a/app/src/main/res/layout/main.xml +++ b/app/src/main/res/layout/main.xml @@ -6,10 +6,7 @@