diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 526b4c2..a2d7c21 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -13,7 +13,6 @@ - diff --git a/.idea/misc.xml b/.idea/misc.xml index 31c79f8..b559a2b 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -4,7 +4,7 @@ diff --git a/app/build.gradle b/app/build.gradle index ca1eef9..0d44ebe 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,12 +3,12 @@ plugins { } android { - compileSdk 31 + compileSdk 33 defaultConfig { applicationId "com.example.co2sense_app" minSdk 26 - targetSdk 31 + targetSdk 33 versionCode 1 versionName "1.0" @@ -25,14 +25,18 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + namespace 'com.example.co2sense_app' } dependencies { - implementation 'androidx.appcompat:appcompat:1.4.1' - implementation 'com.google.android.material:material:1.6.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.3' - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.3' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + implementation 'androidx.appcompat:appcompat:1.5.1' + implementation 'com.google.android.material:material:1.7.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'androidx.activity:activity:1.7.0-alpha02' + implementation 'androidx.fragment:fragment:1.6.0-alpha04' + implementation 'androidx.core:core:1.9.0' + //testImplementation 'junit:junit:4.13.2' + //androidTestImplementation 'androidx.test.ext:junit:1.1.3' + //androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 57d5186..9be11ed 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,10 +1,16 @@ - + + + - + + + + + + + + + + + @@ -22,4 +37,5 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/example/co2sense_app/BLEDevice.java b/app/src/main/java/com/example/co2sense_app/BLEDevice.java index 5c48030..969a4e1 100644 --- a/app/src/main/java/com/example/co2sense_app/BLEDevice.java +++ b/app/src/main/java/com/example/co2sense_app/BLEDevice.java @@ -10,12 +10,11 @@ import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothProfile; -import android.companion.AssociationRequest; -import android.companion.BluetoothDeviceFilter; +import android.bluetooth.le.BluetoothLeScanner; +import android.bluetooth.le.ScanCallback; import android.companion.CompanionDeviceManager; import android.content.Context; import android.content.Intent; -import android.content.IntentSender; import android.content.pm.PackageManager; import android.os.Build; import android.util.Log; @@ -31,36 +30,45 @@ import java.nio.ByteBuffer; import java.util.NoSuchElementException; public abstract class BLEDevice { + private enum ConnectionState {NONE, STARTING_BT, CONNECTING, CONNECTED}; + private ConnectionState state = ConnectionState.NONE; + private static final int REQUEST_BT_PERMISSION = 16535; private static final int REQUEST_ENABLE_BT = 16536; private static final int SELECT_DEVICE_REQUEST_CODE = 16537; private static final int REQUEST_BT_CONNECT_PERMISSION = 16538; + private static final int REQUEST_BT_SCAN_PERMISSION = 16539; + private static final int REQUEST_FINE_LOC_PERMISSION = 16540; private final Activity activity; - private boolean startPairingOnBTStart; + private boolean startConnectOnBTStart; protected BluetoothGatt bluetoothGatt; private BluetoothDevice device; - private final ActivityResultLauncher requestPermissionLauncher; + private BluetoothAdapter bluetoothAdapter; + + private ScanCallback scanCallback; @RequiresApi(api = Build.VERSION_CODES.S) BLEDevice(Activity activity) { this.activity = activity; - requestPermissionLauncher = ((ActivityResultCaller) activity).registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> connect()); - } + ActivityResultLauncher requestPermissionLauncher = ((ActivityResultCaller) activity).registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> connect()); - public void startBT() { - Log.d("bt", "startBT"); Context context = activity.getApplicationContext(); BluetoothManager bluetoothManager = ContextCompat.getSystemService(context, BluetoothManager.class); if (bluetoothManager == null) throw new NoSuchElementException("No Bluetooth manager found"); - BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter(); + bluetoothAdapter = bluetoothManager.getAdapter(); + } + + @RequiresApi(api = Build.VERSION_CODES.S) + public void startBT() { + Log.d("bt", "startBT"); if (bluetoothAdapter == null) throw new NoSuchElementException("No Bluetooth adapter found"); if (bluetoothAdapter.isEnabled()) { - if (startPairingOnBTStart) pair(); + if (startConnectOnBTStart) connect(); } else { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); if (ActivityCompat.checkSelfPermission(activity, Manifest.permission.BLUETOOTH) != PackageManager.PERMISSION_GRANTED) { @@ -71,36 +79,6 @@ public abstract class BLEDevice { } } - protected void pair() { - Log.d("bt", "pair"); - BluetoothDeviceFilter deviceFilter = new BluetoothDeviceFilter.Builder() - //.setNamePattern(Pattern.compile("My device")) - //.addServiceUuid(new ParcelUuid(new UUID(0x123abcL, -1L)), null) - .build(); - AssociationRequest pairingRequest = new AssociationRequest.Builder() - .addDeviceFilter(deviceFilter) - //.setSingleDevice(true) - .build(); - - CompanionDeviceManager deviceManager = (CompanionDeviceManager) activity.getSystemService(Context.COMPANION_DEVICE_SERVICE); - deviceManager.associate(pairingRequest, new CompanionDeviceManager.Callback() { - @Override - public void onDeviceFound(IntentSender chooserLauncher) { - try { - activity.startIntentSenderForResult(chooserLauncher, SELECT_DEVICE_REQUEST_CODE, null, 0, 0, 0 - ); - } catch (IntentSender.SendIntentException e) { - Log.e("MainActivity", "Failed to send intent"); - } - } - - @Override - public void onFailure(CharSequence error) { - Log.e("pair", (String) error); - } - }, null); - } - @SuppressLint("MissingPermission") @RequiresApi(api = Build.VERSION_CODES.S) protected void connect() { @@ -119,6 +97,7 @@ public abstract class BLEDevice { bluetoothGatt.discoverServices(); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { Log.d("bt", "disconnected"); + connect(); } } @@ -150,7 +129,7 @@ public abstract class BLEDevice { switch (requestCode) { case REQUEST_ENABLE_BT: if(resultCode != Activity.RESULT_OK) startBT(); - else if(startPairingOnBTStart) pair(); + else if(startConnectOnBTStart) connect(); break; case SELECT_DEVICE_REQUEST_CODE: Log.d("bt", "device selected"); @@ -166,11 +145,55 @@ public abstract class BLEDevice { } } - public void startPairingOnBTStart(boolean status) { - startPairingOnBTStart = status; + @RequiresApi(api = Build.VERSION_CODES.S) + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults){ + switch (requestCode){ + case REQUEST_BT_SCAN_PERMISSION: + //startScan(scanCallback); + break; + case REQUEST_FINE_LOC_PERMISSION: + startScan(scanCallback); + break; + } + } + + public void startConnectOnBTStart(boolean status) { + startConnectOnBTStart = status; } protected void onServicesDiscovered(){ } + + @RequiresApi(api = Build.VERSION_CODES.S) + public void startScan(ScanCallback callback){ + scanCallback = callback; + Log.d("BLE_device", "startScan: startBT"); + startBT(); + Log.d("BLE_device", "startScan: getPerm"); + + if (ActivityCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + activity.requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_FINE_LOC_PERMISSION); + Log.d("BLE_device", "startScan: asking perms"); + return; + } + if (ActivityCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + activity.requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, REQUEST_FINE_LOC_PERMISSION); + Log.d("BLE_device", "startScan: asking perms"); + return; + } + if (ActivityCompat.checkSelfPermission(activity, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) { + activity.requestPermissions(new String[]{Manifest.permission.BLUETOOTH_SCAN}, REQUEST_BT_SCAN_PERMISSION); + Log.d("BLE device", "startScan: asking perms"); + //return; + } + Log.d("BLE_device", "startScan: "); + BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner(); + bluetoothLeScanner.startScan(callback); + } + + @RequiresApi(api = Build.VERSION_CODES.S) + public void setDeviceFromMAC(String targetDeviceMAC) { + device = bluetoothAdapter.getRemoteDevice(targetDeviceMAC); + } } diff --git a/app/src/main/java/com/example/co2sense_app/MainActivity.java b/app/src/main/java/com/example/co2sense_app/MainActivity.java index b8b4396..26394d5 100644 --- a/app/src/main/java/com/example/co2sense_app/MainActivity.java +++ b/app/src/main/java/com/example/co2sense_app/MainActivity.java @@ -1,13 +1,21 @@ package com.example.co2sense_app; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; import androidx.constraintlayout.widget.ConstraintLayout; +import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.os.Build; import android.os.Bundle; +import android.text.Layout; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; import android.view.View; public class MainActivity extends AppCompatActivity { @@ -19,14 +27,31 @@ public class MainActivity extends AppCompatActivity { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); + Toolbar myToolbar = (Toolbar) findViewById(R.id.toolbar); + myToolbar.showContextMenu(); + myToolbar.showOverflowMenu(); + setSupportActionBar(myToolbar); + getSupportActionBar().setDisplayShowTitleEnabled(false); + + SharedPreferences sharedPref = getSharedPreferences( + getString(R.string.main_shared_pref),Context.MODE_PRIVATE); + String targetDeviceMAC = sharedPref.getString("target_device_mac", null); + Log.d("MAIN", "onCreate: "+targetDeviceMAC); + Log.d("MAIN", "onCreate: "+sharedPref.hashCode()); + sensor = new CO2Sensor(this); - sensor.startPairingOnBTStart(true); + if(targetDeviceMAC != null) { + sensor.setDeviceFromMAC(targetDeviceMAC); + sensor.startConnectOnBTStart(true); + sensor.startBT(); + } ConstraintLayout mainLayout = findViewById(R.id.mainLayout); - OMeter CO2Meter = findViewById(R.id.CO2OMeter); - mainLayout.setOnClickListener(v -> { -// sensor.startBT(); -// sensor.pair(); + + ConstraintLayout CO2_widget = findViewById(R.id.CO2_ES); + OMeter CO2Meter = CO2_widget.findViewById(R.id.OMeter); + CO2_widget.setOnClickListener(v -> { + Log.d("MAIN", "onCreate: click"); CO2Meter.setValue(CO2Meter.getValue() + 100); }); } @@ -43,4 +68,33 @@ public class MainActivity extends AppCompatActivity { super.onActivityResult(requestCode, resultCode, data); sensor.onActivityResult(requestCode, resultCode, data); } + + @RequiresApi(api = Build.VERSION_CODES.S) + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + sensor.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.toolbar, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.select_target: + Intent myIntent = new Intent(this, search_ble_devices_activity.class); + startActivity(myIntent); + return true; + + default: + // If we got here, the user's action was not recognized. + // Invoke the superclass to handle it. + return super.onOptionsItemSelected(item); + + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/co2sense_app/OMeter.java b/app/src/main/java/com/example/co2sense_app/OMeter.java index 658f785..49e53c8 100644 --- a/app/src/main/java/com/example/co2sense_app/OMeter.java +++ b/app/src/main/java/com/example/co2sense_app/OMeter.java @@ -17,13 +17,14 @@ import android.view.View; import androidx.annotation.Nullable; public class OMeter extends View { - private float min, max, value; + private float min, max, value = 100; private float angle; private int minColor, maxColor; private int bandRad; private int strokeWidth; private RectF rectf; private String dispValue = "NaN"; + private int textColor; private final Paint bandPaint; private final Paint textPaint; @@ -48,7 +49,7 @@ public class OMeter extends View { bandPaint.setStrokeCap(Paint.Cap.ROUND); textPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - textPaint.setColor(0xFFFFFFFF); //TODO : get from theme + textPaint.setColor(textColor); textPaint.setTextAlign(Paint.Align.CENTER); } diff --git a/app/src/main/java/com/example/co2sense_app/ble_devices_adapter.java b/app/src/main/java/com/example/co2sense_app/ble_devices_adapter.java new file mode 100644 index 0000000..9b2194a --- /dev/null +++ b/app/src/main/java/com/example/co2sense_app/ble_devices_adapter.java @@ -0,0 +1,74 @@ +package com.example.co2sense_app; + + +import android.bluetooth.le.ScanResult; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.ArrayList; + +public class ble_devices_adapter extends RecyclerView.Adapter { + ArrayList data; + View.OnClickListener itemSelectListener; + + public static class ViewHolder extends RecyclerView.ViewHolder { + private final TextView textView; + private final Button selectButton; + + public ViewHolder(View view) { + super(view); + textView = view.findViewById(R.id.name); + selectButton = view.findViewById(R.id.select_ble_device); + } + + public void setName(String name) { + textView.setText(name); + } + + public void setSelectOnClickListener(View.OnClickListener l){ + selectButton.setOnClickListener(l); + } + } + + public ble_devices_adapter(){ + data = new ArrayList<>(); + } + + public void addDevice(ScanResult result){ + if(data.stream().anyMatch(r -> r.getDevice().getAddress().equals(result.getDevice().getAddress()))) return; + data.add(result); + } + + public void setSelectOnClickListener(View.OnClickListener l){ + itemSelectListener = l; + } + + public ScanResult getDataByIndex(int viewIndex) { + return data.get(viewIndex); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.ble_device_scan_item, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + holder.setName(data.get(position).getScanRecord().getDeviceName()); + holder.setSelectOnClickListener(itemSelectListener); + } + + @Override + public int getItemCount() { + return data.size(); + } +} diff --git a/app/src/main/java/com/example/co2sense_app/search_ble_devices_activity.java b/app/src/main/java/com/example/co2sense_app/search_ble_devices_activity.java new file mode 100644 index 0000000..0d76820 --- /dev/null +++ b/app/src/main/java/com/example/co2sense_app/search_ble_devices_activity.java @@ -0,0 +1,86 @@ +package com.example.co2sense_app; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import android.app.Activity; +import android.bluetooth.le.ScanCallback; +import android.bluetooth.le.ScanResult; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Build; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.ListView; + +public class search_ble_devices_activity extends AppCompatActivity { + + CO2Sensor sensor; + + @RequiresApi(api = Build.VERSION_CODES.S) + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_search_ble_devices); + + RecyclerView resultsView = findViewById(R.id.devicesFoundList); + LinearLayoutManager layoutManager = new LinearLayoutManager(this); + resultsView.setLayoutManager(layoutManager); + + ble_devices_adapter resultsAdapter = new ble_devices_adapter(); + + Activity thisActivity=this; + + resultsAdapter.setSelectOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + SharedPreferences sharedPref = thisActivity.getSharedPreferences( + getString(R.string.main_shared_pref),Context.MODE_PRIVATE); + SharedPreferences.Editor edit = sharedPref.edit(); + int viewIndex = resultsView.getChildAdapterPosition((View)view.getParent()); + ScanResult device = resultsAdapter.getDataByIndex(viewIndex); + String address = device.getDevice().getAddress(); + Log.d("search_ble", "onClick: "+address); + edit.putString("target_device_mac", address); + edit.apply(); + thisActivity.finish(); + } + }); + resultsView.setAdapter(resultsAdapter); + + sensor = new CO2Sensor(this); + Log.d("search_ble", "onCreate: "); + sensor.startScan(new ScanCallback() { + @Override + public void onScanResult(int callbackType, ScanResult result) { + super.onScanResult(callbackType, result); + resultsAdapter.addDevice(result); + resultsAdapter.notifyDataSetChanged(); + Log.d("search_ble", "onScanResult: "+result); + } + }); + } + + @RequiresApi(api = Build.VERSION_CODES.S) + @Override + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + super.onActivityResult(requestCode, resultCode, data); + sensor.onActivityResult(requestCode, resultCode, data); + } + + @RequiresApi(api = Build.VERSION_CODES.S) + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + sensor.onRequestPermissionsResult(requestCode, permissions, grantResults); + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/battery_icon.xml b/app/src/main/res/drawable/battery_icon.xml new file mode 100644 index 0000000..cab3570 --- /dev/null +++ b/app/src/main/res/drawable/battery_icon.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/battery_progressbar.xml b/app/src/main/res/drawable/battery_progressbar.xml new file mode 100644 index 0000000..4d99adc --- /dev/null +++ b/app/src/main/res/drawable/battery_progressbar.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bluetooth_icon.xml b/app/src/main/res/drawable/bluetooth_icon.xml new file mode 100644 index 0000000..0290b3b --- /dev/null +++ b/app/src/main/res/drawable/bluetooth_icon.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/rounded_rectangle.xml b/app/src/main/res/drawable/rounded_rectangle.xml new file mode 100644 index 0000000..169e3c9 --- /dev/null +++ b/app/src/main/res/drawable/rounded_rectangle.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index c1b0efd..cbf55f7 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -7,22 +7,65 @@ android:layout_height="match_parent" tools:context=".MainActivity"> - + app:layout_constraintTop_toTopOf="parent" + app:menu="@menu/toolbar"> - + android:layout_height="match_parent" + android:text="@string/status_erreur" + tools:layout_editor_absoluteX="16dp" + tools:layout_editor_absoluteY="28dp" /> + + + + + + + + + + + + + + + - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_search_ble_devices.xml b/app/src/main/res/layout/activity_search_ble_devices.xml new file mode 100644 index 0000000..98af5ad --- /dev/null +++ b/app/src/main/res/layout/activity_search_ble_devices.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/ble_device_scan_item.xml b/app/src/main/res/layout/ble_device_scan_item.xml new file mode 100644 index 0000000..ae7a798 --- /dev/null +++ b/app/src/main/res/layout/ble_device_scan_item.xml @@ -0,0 +1,26 @@ + + + + + +