package org.ostrya.presencepublisher.ui.dialog; import android.app.Dialog; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.os.Build; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.widget.ProgressBar; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.DialogFragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import org.altbeacon.beacon.Beacon; import org.altbeacon.beacon.BeaconManager; import org.altbeacon.beacon.BeaconParser; import org.altbeacon.beacon.service.scanner.CycledLeScanCallback; import org.altbeacon.beacon.service.scanner.CycledLeScanner; import org.ostrya.presencepublisher.R; import java.util.List; import java.util.Set; @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) public class BeaconScanDialogFragment extends DialogFragment { private static final long SCAN_TIME_MILLIS = 20_000; private CycledLeScanner leScanner; private List<BeaconParser> beaconParsers; private DialogCallback dialogCallback; private Set<String> knownBeacons; private ProgressBar progressBar; private BeaconListAdapter adapter; public static BeaconScanDialogFragment getInstance(Context context, DialogCallback dialogCallback, Set<String> knownBeacons) { BeaconScanDialogFragment fragment = new BeaconScanDialogFragment(); fragment.setLeScanner(CycledLeScanner.createScanner(context, SCAN_TIME_MILLIS, 0L, false, fragment.new ScanCallback(), null)); fragment.setBeaconParsers(BeaconManager.getInstanceForApplication(context.getApplicationContext()).getBeaconParsers()); fragment.setDialogCallback(dialogCallback); fragment.setKnownBeacons(knownBeacons); return fragment; } @NonNull @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { LayoutInflater inflater = LayoutInflater.from(requireContext()); View view = inflater.inflate(R.layout.dialog_beacon_scan, null); progressBar = view.findViewById(R.id.beaconScanProgress); progressBar.setIndeterminate(true); adapter = new BeaconListAdapter(this::onSelect, knownBeacons); RecyclerView recyclerView = view.findViewById(R.id.beaconScanList); Context context = view.getContext(); LinearLayoutManager layout = new LinearLayoutManager(context); recyclerView.setLayoutManager(layout); recyclerView.setAdapter(adapter); recyclerView.setHasFixedSize(false); recyclerView.setItemAnimator(null); AlertDialog.Builder builder = new AlertDialog.Builder(requireContext()); builder.setTitle(R.string.dialog_scan_beacons_title) .setView(view) .setNegativeButton(R.string.dialog_cancel, (dialog, which) -> { leScanner.stop(); dialogCallback.accept(null); }); return builder.create(); } private void onSelect(Beacon beacon) { dialogCallback.accept(beacon); dismiss(); } private void setLeScanner(CycledLeScanner leScanner) { this.leScanner = leScanner; this.leScanner.start(); } public void setBeaconParsers(List<BeaconParser> beaconParsers) { this.beaconParsers = beaconParsers; } private void setDialogCallback(DialogCallback dialogCallback) { this.dialogCallback = dialogCallback; } private void setKnownBeacons(Set<String> knownBeacons) { this.knownBeacons = knownBeacons; } public interface DialogCallback { void accept(@Nullable Beacon beacon); } private class ScanCallback implements CycledLeScanCallback { @Override public void onLeScan(BluetoothDevice bluetoothDevice, int rssi, byte[] scanRecord) { Beacon beacon = null; for (BeaconParser parser : beaconParsers) { beacon = parser.fromScanData(scanRecord, rssi, bluetoothDevice); if (beacon != null) { break; } } if (beacon != null && beacon.getBluetoothName() != null && beacon.getBluetoothAddress() != null) { adapter.addBeacon(beacon); } } @Override public void onCycleEnd() { leScanner.stop(); progressBar.setVisibility(View.GONE); } } }