Commit aecafe2e authored by Mark Harman's avatar Mark Harman
Browse files

Bluetooth LE (Kraken Smart Housing) support.

parent 8460c1a9
......@@ -236,6 +236,7 @@ your wedding etc :)</p>
<ul>
<li>App icon and take_photo.png, take_photo_pressed.png by <a href="http://www.yeti-designs.com">Adam Lapinski</a>.</li>
<li>Improvements/help for video frame rates (including high speed) by George Joseph.</li>
<li>Kraken Smart Housing Bluetooth LE support by Edouard Lafargue</li>
<li>Improved selfie stick button support by Lau Keat Hwa.</li>
<li>Option for filenames to be based on UTC (Zulu) time by <a href="https://www.cathedralcanyon.net">David Pletcher</a> ( lpm_sourceforge AT cathedralcanyon DOT net ).</li>
<li>Support for manual ISO for old camera API on Asus Zenphone 2 Z00A and Z008 by Fl&aacute;vio Keglevich ( fkeglevich AT gmail DOT com ).</li>
......
......@@ -8,6 +8,8 @@
<supports-screens android:xlargeScreens="true" android:largeScreens="true" android:normalScreens="true" android:smallScreens="true" android:anyDensity="true"/>
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
......@@ -15,7 +17,8 @@
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.microphone" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
......@@ -64,6 +67,16 @@
</intent-filter>
<meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts" />
</activity>
<activity
android:name="net.sourceforge.opencamera.Remotecontrol.DeviceScanner"
android:label="@string/scan_ble"
>
<intent-filter>
<action android:name="net.sourceforge.opencamera.Remotecontrol.DeviceScanner"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<!-- should not change the android:name, including moving to a subpackage - see http://android-developers.blogspot.co.uk/2011/06/things-that-cannot-change.html -->
<activity
android:name="TakePhoto"
......@@ -132,5 +145,6 @@
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
</service>
<service android:name="net.sourceforge.opencamera.Remotecontrol.BluetoothLeService" android:enabled="true"/>
</application>
</manifest>
......@@ -4,6 +4,7 @@ import net.sourceforge.opencamera.CameraController.CameraController;
import net.sourceforge.opencamera.CameraController.CameraControllerManager2;
import net.sourceforge.opencamera.Preview.Preview;
import net.sourceforge.opencamera.Preview.VideoProfile;
import net.sourceforge.opencamera.Remotecontrol.BluetoothLeService;
import net.sourceforge.opencamera.UI.FolderChooserDialog;
import net.sourceforge.opencamera.UI.MainUI;
import net.sourceforge.opencamera.UI.ManualSeekbars;
......@@ -21,7 +22,9 @@ import java.util.Map;
import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
......@@ -38,6 +41,7 @@ import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.StatFs;
import android.preference.PreferenceManager;
......@@ -92,6 +96,10 @@ public class MainActivity extends Activity {
private Sensor mSensorAccelerometer;
private Sensor mSensorMagnetic;
private MainUI mainUI;
private BluetoothLeService mBluetoothLeService;
private String mRemoteDeviceAddress;
private String mRemoteDeviceType;
private Boolean mRemoteConnected = false;
private PermissionHandler permissionHandler;
private SettingsManager settingsManager;
private SoundPoolManager soundPoolManager;
......@@ -154,6 +162,38 @@ public class MainActivity extends Activity {
public volatile String test_last_saved_image;
public static boolean test_force_supports_camera2; // okay to be static, as this is set for an entire test suite
public volatile String test_save_settings_file;
private static final float WATER_DENSITY_FRESHWATER = 1.0f;
private static final float WATER_DENSITY_SALTWATER = 1.03f;
private float mWaterDensity = 1.0f;
// Code to manage Service lifecycle for remote control.
private final ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder service) {
mBluetoothLeService = ((BluetoothLeService.LocalBinder) service).getService();
if (!mBluetoothLeService.initialize()) {
Log.e(TAG, "Unable to initialize Bluetooth");
finish();
}
// Automatically connects to the device upon successful start-up initialization.
mBluetoothLeService.connect(mRemoteDeviceAddress);
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
public void run() {
mBluetoothLeService.connect(mRemoteDeviceAddress);
}
}, 5000);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
......@@ -527,6 +567,9 @@ public class MainActivity extends Activity {
}
}).start();
// Last: if BLE remote control is enabled, then start the background BLE service
startRemoteControl();
if( MyDebug.LOG )
Log.d(TAG, "onCreate: total time for Activity startup: " + (System.currentTimeMillis() - debug_time));
}
......@@ -761,6 +804,8 @@ public class MainActivity extends Activity {
// null from beneath applicationInterface.onDestroy()
waitUntilImageQueueEmpty();
stopRemoteControl();
preview.onDestroy();
if( applicationInterface != null ) {
applicationInterface.onDestroy();
......@@ -1086,7 +1131,203 @@ public class MainActivity extends Activity {
}
};
@Override
/**
* Receives event from the remote command handler through intents
* Handles various events fired by the Service.
*
* TODO: factor this out of MainActivity and into the Remotecontrol namespace
*/
private final BroadcastReceiver remoteControlCommandReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
if( MyDebug.LOG )
Log.d(TAG, "Remote connected");
// Tell the Bluetooth service what type of remote we want to use
mBluetoothLeService.setRemoteDeviceType(mRemoteDeviceType);
setBrightnessForCamera(false);
} else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
if( MyDebug.LOG )
Log.d(TAG, "Remote disconnected");
mRemoteConnected = false;
applicationInterface.getDrawPreview().onExtraOSDValuesChanged("-- \u00B0C", "-- m");
mainUI.updateRemoteConnectionIcon();
setBrightnessToMinimumIfWanted();
if (mainUI.isExposureUIOpen())
mainUI.toggleExposureUI();
} else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
if( MyDebug.LOG )
Log.d(TAG, "Remote services discovered");
/*
We let the BluetoothLEService subscribe to what is relevant, so we
do nothing here, but we wait until this is done to update the UI
icon
*/
mRemoteConnected = true;
mainUI.updateRemoteConnectionIcon();
} else if (BluetoothLeService.ACTION_SENSOR_VALUE.equals(action)) {
double temp = intent.getDoubleExtra(BluetoothLeService.SENSOR_TEMPERATURE, -1);
double depth = intent.getDoubleExtra(BluetoothLeService.SENSOR_DEPTH, -1) / mWaterDensity;
depth = (Math.round(depth* 10)) / 10.0; // Round to 1 decimal
if( MyDebug.LOG )
Log.d(TAG, "Sensor values: depth: " + depth + " - temp: " + temp);
// Create two OSD lines
String line1 = "" + temp + " \u00B0C";
String line2 = "" + depth + " m";
applicationInterface.getDrawPreview().onExtraOSDValuesChanged(line1, line2);
} else if (BluetoothLeService.ACTION_REMOTE_COMMAND.equals(action)) {
int command = intent.getIntExtra(BluetoothLeService.EXTRA_DATA, -1);
// TODO: we could abstract this into a method provided by each remote control model
switch (command) {
case BluetoothLeService.COMMAND_SHUTTER:
// Easiest - just take a picture (or start/stop camera)
MainActivity.this.takePicture(false);
break;
case BluetoothLeService.COMMAND_MODE:
// "Mode" key :either toggles photo/video mode, or
// closes the settings screen that is currently open
if (mainUI.popupIsOpen()) {
mainUI.togglePopupSettings();
} else if (mainUI.isExposureUIOpen()) {
mainUI.toggleExposureUI();
} else {
clickedSwitchVideo(null);
}
break;
case BluetoothLeService.COMMAND_MENU:
// Open the exposure UI (ISO/Exposure) or
// select the current line on an open UI or
// select the current option on a button on a selected line
if (!mainUI.popupIsOpen()) {
if (! mainUI.isExposureUIOpen()) {
mainUI.toggleExposureUI();
} else {
if (mainUI.isSelectingExposureUIElement()) {
// Close Exposure UI if new press on MENU
// while already selecting
mainUI.toggleExposureUI();
} else {
// Select current element in Exposure UI
mainUI.selectExposureUILine();
}
}
} else {
if (mainUI.selectingIcons()) {
mainUI.clickSelectedIcon();
} else {
mainUI.highlightPopupIcon(true, false);
}
}
break;
case BluetoothLeService.COMMAND_UP:
if (!mainUI.processRemoteUpButton()) {
// Default up behaviour:
// - if we are on manual focus, then adjust focus.
// - if we are on autofocus, then adjust zoom.
if (getPreview().getCurrentFocusValue() != null && getPreview().getCurrentFocusValue().equals("focus_mode_manual2")) {
changeFocusDistance(-25, false);
} else {
// Adjust zoom
zoomIn();
}
}
break;
case BluetoothLeService.COMMAND_DOWN:
if (!mainUI.processRemoteDownButton()) {
if (getPreview().getCurrentFocusValue() != null && getPreview().getCurrentFocusValue().equals("focus_mode_manual2")) {
changeFocusDistance(25, false);
} else {
// Adjust zoom
zoomOut();
}
}
break;
case BluetoothLeService.COMMAND_AFMF:
// Open the camera settings popup menu (not the app settings)
// or selects the current line/icon in the popup menu, and finally
// clicks the icon
//if (!mainUI.popupIsOpen()) {
mainUI.togglePopupSettings();
//}
break;
default:
break;
}
} else {
if( MyDebug.LOG )
Log.d(TAG, "Other remote event");
}
}
};
public Boolean remoteConnected() {
/*if( true )
return true; // test*/
return mRemoteConnected;
}
// TODO: refactor for a filter than receives generic remote control intents
private static IntentFilter makeRemoteCommandIntentFilter() {
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED);
intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED);
intentFilter.addAction(BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED);
intentFilter.addAction(BluetoothLeService.ACTION_DATA_AVAILABLE);
intentFilter.addAction(BluetoothLeService.ACTION_REMOTE_COMMAND);
intentFilter.addAction(BluetoothLeService.ACTION_SENSOR_VALUE);
return intentFilter;
}
/**
* Starts or stops the remote control layer
*/
private void startRemoteControl() {
if( MyDebug.LOG )
Log.d(TAG, "BLE Remote control service start check...");
Intent gattServiceIntent = new Intent(this, BluetoothLeService.class);
if ( remoteEnabled()) {
if( MyDebug.LOG )
Log.d(TAG, "Remote enabled, starting service");
bindService(gattServiceIntent, mServiceConnection, BIND_AUTO_CREATE);
registerReceiver(remoteControlCommandReceiver, makeRemoteCommandIntentFilter());
} else {
if( MyDebug.LOG )
Log.d(TAG, "Remote disabled, stopping service");
// Stop the service if necessary
try{
unregisterReceiver(remoteControlCommandReceiver);
unbindService(mServiceConnection);
mRemoteConnected = false; // Unbinding closes the connection, of course
mainUI.updateRemoteConnectionIcon();
} catch (IllegalArgumentException e){
if( MyDebug.LOG )
Log.d(TAG, "Remote Service was not running, that's fine");
}
}
}
private void stopRemoteControl() {
if( MyDebug.LOG )
Log.d(TAG, "BLE Remote control service shutdown...");
Intent gattServiceIntent = new Intent(this, BluetoothLeService.class);
if ( remoteEnabled()) {
// Stop the service if necessary
try{
unregisterReceiver(remoteControlCommandReceiver);
unbindService(mServiceConnection);
mRemoteConnected = false; // Unbinding closes the connection, of course
mainUI.updateRemoteConnectionIcon();
} catch (IllegalArgumentException e){
Log.e(TAG, "Remote Service was not running, that's strange");
e.printStackTrace();
}
}
}
@Override
protected void onResume() {
long debug_time = 0;
if( MyDebug.LOG ) {
......@@ -1438,6 +1679,11 @@ public class MainActivity extends Activity {
return cameraId;
}
/**
* Selects the next camera on the phone - in practice, switches between
* front and back cameras
* @param view
*/
public void clickedSwitchCamera(View view) {
if( MyDebug.LOG )
Log.d(TAG, "clickedSwitchCamera");
......@@ -1459,6 +1705,10 @@ public class MainActivity extends Activity {
}
}
/**
* Toggles Photo/Video mode
* @param view
*/
public void clickedSwitchVideo(View view) {
if( MyDebug.LOG )
Log.d(TAG, "clickedSwitchVideo");
......@@ -1621,6 +1871,19 @@ public class MainActivity extends Activity {
if( MyDebug.LOG )
Log.d(TAG, "this change doesn't require update");
break;
case PreferenceKeys.EnableRemote:
startRemoteControl();;
break;
case PreferenceKeys.RemoteName:
// The remote address changed, restart the service
if (remoteEnabled())
stopRemoteControl();
startRemoteControl();
break;
case PreferenceKeys.WaterType:
Boolean wt = sharedPreferences.getBoolean(PreferenceKeys.WaterType, true);
mWaterDensity = wt ? WATER_DENSITY_SALTWATER : WATER_DENSITY_FRESHWATER;
break;
default:
if( MyDebug.LOG )
Log.d(TAG, "this change does require update");
......@@ -2211,6 +2474,29 @@ public class MainActivity extends Activity {
});
}
/**
* Set the brightness to minimal in case the preference key is set to do it
*/
void setBrightnessToMinimumIfWanted() {
if( MyDebug.LOG )
Log.d(TAG, "setBrightnessToMinimum");
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
final WindowManager.LayoutParams layout = getWindow().getAttributes();
if( sharedPreferences.getBoolean(PreferenceKeys.DimWhenDisconnectedPreferenceKey, false) ) {
layout.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_OFF;
}
else {
layout.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE;
}
this.runOnUiThread(new Runnable() {
public void run() {
getWindow().setAttributes(layout);
}
});
}
/** Sets the window flags for normal operation (when camera preview is visible).
*/
public void setWindowFlagsForCamera() {
......@@ -4335,6 +4621,20 @@ public class MainActivity extends Activity {
freeSpeechRecognizer();
}
}
/**
* Checks if remote control is enabled in the settings, and the remote control address
* is also defined
* @return true if this is the case
*/
public boolean remoteEnabled() {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
Boolean remote_enabled = sharedPreferences.getBoolean(PreferenceKeys.EnableRemote, false);
mRemoteDeviceType = sharedPreferences.getString(PreferenceKeys.RemoteType, "undefined");
mRemoteDeviceAddress = sharedPreferences.getString(PreferenceKeys.RemoteName, "undefined");
return remote_enabled && !mRemoteDeviceAddress.equals("undefined");
}
public boolean hasAudioControl() {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
......
......@@ -145,6 +145,14 @@ public class PreferenceKeys {
public static final String FrontCameraMirrorKey = "preference_front_camera_mirror";
public static final String EnableRemote = "preference_enable_remote";
public static final String RemoteName = "preference_remote_device_name";
public static final String RemoteType = "preference_remote_type";
public static final String WaterType = "preference_water_type";
//public static final String BackgroundPhotoSavingPreferenceKey = "preference_background_photo_saving";
public static final String Camera2FakeFlashPreferenceKey = "preference_camera2_fake_flash";
......@@ -165,6 +173,8 @@ public class PreferenceKeys {
public static final String TakePhotoBorderPreferenceKey = "preference_take_photo_border";
public static final String DimWhenDisconnectedPreferenceKey = "preference_remote_disconnect_screen_dim";
public static String getShowWhenLockedPreferenceKey() {
return "preference_show_when_locked";
}
......
package net.sourceforge.opencamera.Remotecontrol;
import net.sourceforge.opencamera.MyDebug;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
public class BluetoothLeService extends Service {
private final static String TAG = BluetoothLeService.class.getSimpleName();
private BluetoothManager mBluetoothManager;
private BluetoothAdapter mBluetoothAdapter;
private String mBluetoothDeviceAddress;
private BluetoothGatt mBluetoothGatt;
private String mRemoteDeviceType;
private int mConnectionState = STATE_DISCONNECTED;
private HashMap<String, BluetoothGattCharacteristic> subscribedCharacteristics = new HashMap<>();
private List<BluetoothGattCharacteristic> charsToSubscribeTo = new ArrayList<>();
private double currentTemp = -1;
private double currentDepth = -1;
private static final int STATE_DISCONNECTED = 0;
private static final int STATE_CONNECTING = 1;
private static final int STATE_CONNECTED = 2;
public final static String ACTION_GATT_CONNECTED =
"net.sourceforge.opencamera.Remotecontrol.ACTION_GATT_CONNECTED";
public final static String ACTION_GATT_DISCONNECTED =
"net.sourceforge.opencamera.Remotecontrol.ACTION_GATT_DISCONNECTED";
public final static String ACTION_GATT_SERVICES_DISCOVERED =
"net.sourceforge.opencamera.Remotecontrol.ACTION_GATT_SERVICES_DISCOVERED";
public final static String ACTION_DATA_AVAILABLE =
"net.sourceforge.opencamera.Remotecontrol.ACTION_DATA_AVAILABLE";
public final static String ACTION_REMOTE_COMMAND =
"net.sourceforge.opencamera.Remotecontrol.COMMAND";
public final static String ACTION_SENSOR_VALUE =
"net.sourceforge.opencamera.Remotecontrol.SENSOR";
public final static String SENSOR_TEMPERATURE =
"net.sourceforge.opencamera.Remotecontrol.TEMPERATURE";
public final static String SENSOR_DEPTH =
"net.sourceforge.opencamera.Remotecontrol.DEPTH";
public final static String EXTRA_DATA =
"net.sourceforge.opencamera.Remotecontrol.EXTRA_DATA";
public final static int COMMAND_SHUTTER = 32;
public final static int COMMAND_MODE = 16;
public final static int COMMAND_MENU = 48;
public final static int COMMAND_AFMF = 97;
public final static int COMMAND_UP = 64;
public final static int COMMAND_DOWN = 80;
public void setRemoteDeviceType(String remoteDeviceType) {
if( MyDebug.LOG )
Log.d(TAG, "Setting remote type: " + remoteDeviceType);
mRemoteDeviceType = remoteDeviceType;
}
// Various callback methods defined by the BLE API.
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
String intentAction;
if (newState == BluetoothProfile.STATE_CONNECTED) {
intentAction = ACTION_GATT_CONNECTED;
mConnectionState = STATE_CONNECTED;
broadcastUpdate(intentAction);
if( MyDebug.LOG ) {
Log.d(TAG, "Connected to GATT server.");
Log.d(TAG, "Attempting to start service discovery");
}
mBluetoothGatt.discoverServices();
currentDepth = -1;
currentTemp = -1;
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
intentAction = ACTION_GATT_DISCONNECTED;
mConnectionState = STATE_DISCONNECTED;
if( MyDebug.LOG )
Log.d(TAG, "Disconnected from GATT server, reattempting every 5 seconds.");
broadcastUpdate(intentAction);
attemptReconnect();
}
}
public void attemptReconnect() {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
public void run() {
if( MyDebug.LOG )
Log.d(TAG, "Attempting to reconnect to remote.");
connect(mBluetoothDeviceAddress);
}
}, 5000);
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
subscribeToServices();
} else {
if( MyDebug.LOG )
Log.d(TAG, "onServicesDiscovered received: " + status);
}
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
if (MyDebug.LOG)
Log.d(TAG,"Got notification");
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
@Override
public void onDescriptorWrite (BluetoothGatt gatt,
BluetoothGattDescriptor descriptor,
int status) {
// We need to wait for this callback before enabling the next notification in case we
// have several in our list
if (!charsToSubscribeTo.isEmpty()) {
setCharacteristicNotification(charsToSubscribeTo.remove(0), true);
}
}
};
/**
* Subscribe to the services/characteristics we need depending
* on the remote device model
*
*/
private void subscribeToServices() {
List<BluetoothGattService> gattServices = getSupportedGattServices();
if (gattServices == null) return;
UUID uuid = null;
List<UUID> mCharacteristicsWanted;
switch (mRemoteDeviceType) {
case "preference_remote_type_kraken":
mCharacteristicsWanted = KrakenGattAttributes.getDesiredCharacteristics();
break;
default:
mCharacteristicsWanted = Arrays.asList(UUID.fromString("0000"));
break;
}
// Loops through available GATT Services and characteristics, and subscribe to
// the ones we want. Today, we just enable notifications since that's all we need.
for (BluetoothGattService gattService : gattServices) {
List<BluetoothGattCharacteristic> gattCharacteristics =
gattService.getCharacteristics();
// Loops through available Characteristics.
for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
uuid = gattCharacteristic.getUuid();
if (mCharacteristicsWanted.contains(uuid)) {
if( MyDebug.LOG )
Log.d(TAG, "Found characteristic to subscribe to: " + uuid);
charsToSubscribeTo.add(gattCharacteristic);
}
}
}
// We need to enable notifications asynchronously
setCharacteristicNotification(charsToSubscribeTo.remove(0), true);
}
private void broadcastUpdate(final String action) {
final Intent intent = new Intent(action);
sendBroadcast(intent);
}
private void broadcastUpdate( String action,
final BluetoothGattCharacteristic characteristic) {
UUID uuid = characteristic.getUuid();
final int format_uint8 = BluetoothGattCharacteristic.FORMAT_UINT8;
final int format_uint16 = BluetoothGattCharacteristic.FORMAT_UINT16;
int remoteCommand = -1;
if (KrakenGattAttributes.KRAKEN_BUTTONS_CHARACTERISTIC.equals(uuid)) {
if( MyDebug.LOG )
Log.d(TAG,"Got Kraken button press");
final int buttonCode= characteristic.getIntValue(format_uint8, 0);
if( MyDebug.LOG )
Log.d(TAG, String.format("Received Button press: %d", buttonCode));
// Note: we stay at a fairly generic level here and will manage variants
// on the various button actions in MainActivity, because those will change depending
// on the current state of the app, and we don't want to know anything about that state
// from the Bluetooth LE service
// TODO: update to remove all those tests and just forward buttonCode since value is identical
// but this is more readable if we want to implement other drivers
if (buttonCode == 32) {
// Shutter press
remoteCommand = COMMAND_SHUTTER;
} else if (buttonCode == 16) {
// "Mode" button: either "back" action or "Photo/Camera" switch
remoteCommand = COMMAND_MODE;
} else if (buttonCode == 48) {
// "Menu" button
remoteCommand = COMMAND_MENU;
} else if (buttonCode == 97) {
// AF/MF button
remoteCommand = COMMAND_AFMF;
} else if (buttonCode == 96) {
// Long press on MF/AF button.
// Note: the camera issues button code 97 first, then
// 96 after one second of continuous press
} else if (buttonCode == 64) {
// Up button
remoteCommand = COMMAND_UP;
} else if (buttonCode == 80) {
// Down button
remoteCommand = COMMAND_DOWN;
}
// Only send forward if we have something to say
if (remoteCommand > -1) {
final Intent intent = new Intent(ACTION_REMOTE_COMMAND);
intent.putExtra(EXTRA_DATA, remoteCommand);
sendBroadcast(intent);
}
} else if (KrakenGattAttributes.KRAKEN_SENSORS_CHARACTERISTIC.equals(uuid)) {
// The housing returns four bytes.
// Byte 0-1: depth = (Byte 0 + Byte 1 << 8) / 10 / density
// Byte 2-3: temperature = (Byte 2 + Byte 3 << 8) / 10
//
// Depth is valid for fresh water by default ( makes you wonder whether the sensor
// is really designed for saltwater at all), and the value has to be divided by the density
// of saltwater. A commonly accepted value is 1030 kg/m3 (1.03 density)
double temperature = characteristic.getIntValue(format_uint16, 2) / 10;
double depth = characteristic.getIntValue(format_uint16, 0) / 10;
if (temperature == currentTemp && depth == currentDepth)
return;
currentDepth = depth;
currentTemp = temperature;
if (MyDebug.LOG)
Log.d(TAG, "Got new Kraken sensor reading. Temperature: " + temperature + " Depth:" + depth);
final Intent intent = new Intent(ACTION_SENSOR_VALUE);
intent.putExtra(SENSOR_TEMPERATURE, temperature);
intent.putExtra(SENSOR_DEPTH, depth);
sendBroadcast(intent);
}
}
public class LocalBinder extends Binder {
public BluetoothLeService getService() {
return BluetoothLeService.this;
}
}
private final IBinder mBinder = new LocalBinder();
@Override
public IBinder onBind(Intent intent) {
if( MyDebug.LOG )
Log.d(TAG, "Starting OpenCamera Bluetooth Service");
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
close();
return super.onUnbind(intent);
}
public boolean initialize() {
if (mBluetoothManager == null) {
mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
if (mBluetoothManager == null) {
Log.e(TAG, "Unable to initialize BluetoothManager.");
return false;
}
}
mBluetoothAdapter = mBluetoothManager.getAdapter();
if (mBluetoothAdapter == null) {
Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
return false;
}
return true;
}
public boolean connect(final String address) {
if( MyDebug.LOG )
Log.d(TAG, "connect: " + address);
if( mBluetoothAdapter == null ) {
if( MyDebug.LOG )
Log.d(TAG, "mBluetoothAdapter is null");
return false;
}
else if( address == null ) {
if( MyDebug.LOG )
Log.d(TAG, "address is null");
return false;
}
if( mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress) && mBluetoothGatt != null ) {
mBluetoothGatt.disconnect();
mBluetoothGatt.close();
mBluetoothGatt = null;
}
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
if( device == null ) {
if( MyDebug.LOG )
Log.d(TAG, "device not found");
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
public void run() {
if( MyDebug.LOG )
Log.d(TAG, "attempt to connect to remote");
connect(address);
}
}, 5000);
return false;
}
mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
mBluetoothDeviceAddress = address;
mConnectionState = STATE_CONNECTING;
return true;
}
public void close() {
if( mBluetoothGatt == null ) {
return;
}
mBluetoothGatt.close();
mBluetoothGatt = null;
}
public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
boolean enabled) {
if( mBluetoothAdapter == null ) {
if( MyDebug.LOG )
Log.d(TAG, "mBluetoothAdapter is null");
return;
}
else if( mBluetoothGatt == null ) {
if( MyDebug.LOG )
Log.d(TAG, "mBluetoothGatt is null");
return;
}
String uuid = characteristic.getUuid().toString();
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
if (enabled) {
subscribedCharacteristics.put(uuid, characteristic);
} else {
subscribedCharacteristics.remove(uuid);
}
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(KrakenGattAttributes.CLIENT_CHARACTERISTIC_CONFIG);
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
}
public List<BluetoothGattService> getSupportedGattServices() {
if (mBluetoothGatt == null) return null;
return mBluetoothGatt.getServices();
}
}
package net.sourceforge.opencamera.Remotecontrol;
import android.Manifest;
import android.app.Activity;
import android.app.ListActivity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import net.sourceforge.opencamera.MyDebug;
import net.sourceforge.opencamera.PreferenceKeys;
import net.sourceforge.opencamera.R;
import java.util.ArrayList;
/**
* Preference fragment for scanning and displaying available Bluetooth LE devices.
* The result is saved in app preferences
*/
public class DeviceScanner extends ListActivity {
private static final String TAG = "OC-BLEScanner";
private LeDeviceListAdapter mLeDeviceListAdapter;
private BluetoothAdapter mBluetoothAdapter;
private boolean mScanning;
private Handler mHandler;
Button startScanningButton;
private SharedPreferences mSharedPreferences;
private static final int REQUEST_ENABLE_BT = 1;
private static final int REQUEST_LOCATION_PERMISSIONS = 2;
// Stops scanning after 10 seconds.
private static final long SCAN_PERIOD = 10000;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_device_select);
mHandler = new Handler();
if( !getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) ) {
Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
finish();
}
final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
// Checks if Bluetooth is supported on the device.
if( mBluetoothAdapter == null ) {
Toast.makeText(this, R.string.bluetooth_not_supported, Toast.LENGTH_SHORT).show();
finish();
return;
}
startScanningButton = (Button) findViewById(R.id.StartScanButton);
startScanningButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
startScanning();
}
});
mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this.getApplicationContext());
String preference_remote_device_name = PreferenceKeys.RemoteName;
String remote_name = mSharedPreferences.getString(preference_remote_device_name, "none");
if( MyDebug.LOG )
Log.d(TAG, "preference_remote_device_name: " + remote_name);
TextView currentRemote = findViewById(R.id.currentRemote);
currentRemote.setText("Current remote: " + remote_name);
}
protected void startScanning() {
if( MyDebug.LOG )
Log.d(TAG, "Start scanning");
// Ensures Bluetooth is enabled on the device. If Bluetooth is not currently enabled,
// fire an intent to display a dialog asking the user to grant permission to enable it.
if (!mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
// Initializes list view adapter.
mLeDeviceListAdapter = new LeDeviceListAdapter();
setListAdapter(mLeDeviceListAdapter);
// In real life most of bluetooth LE devices associated with location, so without this
// permission the sample shows nothing in most cases
int permissionCoarse = Build.VERSION.SDK_INT >= 23 ?
ContextCompat
.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) :
PackageManager.PERMISSION_GRANTED;
if (permissionCoarse == PackageManager.PERMISSION_GRANTED) {
scanLeDevice(true);
} else {
askForCoarseLocationPermission();
}
}
private void askForCoarseLocationPermission() {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},
REQUEST_LOCATION_PERMISSIONS);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
switch (requestCode) {
case REQUEST_LOCATION_PERMISSIONS: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
scanLeDevice(true);
}
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// User chose not to enable Bluetooth.
if (requestCode == REQUEST_ENABLE_BT && resultCode == Activity.RESULT_CANCELED) {
finish();
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
@Override
protected void onPause() {
if( MyDebug.LOG )
Log.d(TAG, "pause...");
super.onPause();
if (mScanning) {
scanLeDevice(false);
mLeDeviceListAdapter.clear();
}
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
final BluetoothDevice device = mLeDeviceListAdapter.getDevice(position);
if (device == null) return;
if( MyDebug.LOG ) {
Log.d(TAG, "onListItemClick");
Log.d(TAG, device.getAddress());
}
String preference_remote_device_name = PreferenceKeys.RemoteName;
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putString(preference_remote_device_name, device.getAddress());
editor.apply();
scanLeDevice(false);
finish();
// intent.putExtra(DeviceControlActivity.EXTRAS_DEVICE_NAME, device.getName());
}
private void scanLeDevice(final boolean enable) {
if (enable) {
// Stops scanning after a pre-defined scan period.
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
invalidateOptionsMenu();
}
}, SCAN_PERIOD);
mScanning = true;
mBluetoothAdapter.startLeScan(mLeScanCallback);
} else {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
invalidateOptionsMenu();
}
private class LeDeviceListAdapter extends BaseAdapter {
private ArrayList<BluetoothDevice> mLeDevices;
private LayoutInflater mInflator;
public LeDeviceListAdapter() {
super();
mLeDevices = new ArrayList<BluetoothDevice>();
mInflator = DeviceScanner.this.getLayoutInflater();
}
public void addDevice(BluetoothDevice device) {
if(!mLeDevices.contains(device)) {
mLeDevices.add(device);
}
}
public BluetoothDevice getDevice(int position) {
return mLeDevices.get(position);
}
public void clear() {
mLeDevices.clear();
}
@Override
public int getCount() {
return mLeDevices.size();
}
@Override
public Object getItem(int i) {
return mLeDevices.get(i);
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder viewHolder;
// General ListView optimization code.
if (view == null) {
view = mInflator.inflate(R.layout.listitem_device, null);
viewHolder = new ViewHolder();
viewHolder.deviceAddress = (TextView) view.findViewById(R.id.device_address);
viewHolder.deviceName = (TextView) view.findViewById(R.id.device_name);
view.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) view.getTag();
}
BluetoothDevice device = mLeDevices.get(i);
final String deviceName = device.getName();
if (deviceName != null && deviceName.length() > 0)
viewHolder.deviceName.setText(deviceName);
else
viewHolder.deviceName.setText(R.string.unknown_device);
viewHolder.deviceAddress.setText(device.getAddress());
return view;
}
}
// Device scan callback.
private BluetoothAdapter.LeScanCallback mLeScanCallback =
new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
runOnUiThread(new Runnable() {
@Override
public void run() {
mLeDeviceListAdapter.addDevice(device);
mLeDeviceListAdapter.notifyDataSetChanged();
}
});
}
};
static class ViewHolder {
TextView deviceName;
TextView deviceAddress;
}
}
\ No newline at end of file
package net.sourceforge.opencamera.Remotecontrol;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
/**
* This class includes the GATT attributes of the Kraken Smart Housing, which is
* an underwater camera housing that communicates its key presses with the phone over
* Bluetooth Low Energy
*/
public class KrakenGattAttributes {
private static HashMap<String, String> attributes = new HashMap();
// The Kraken Smart Housing advertises itself as a heart measurement device, talk about
// lazy devs...
public static UUID HEART_RATE_MEASUREMENT = UUID.fromString("00002a37-0000-1000-8000-00805f9b34fb");
public static UUID CLIENT_CHARACTERISTIC_CONFIG = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
public static UUID KRAKEN_SENSORS_SERVICE = UUID.fromString("00001623-1212-efde-1523-785feabcd123");
public static UUID KRAKEN_SENSORS_CHARACTERISTIC = UUID.fromString("00001625-1212-efde-1523-785feabcd123");
public static UUID KRAKEN_BUTTONS_SERVICE= UUID.fromString("00001523-1212-efde-1523-785feabcd123");
public static UUID KRAKEN_BUTTONS_CHARACTERISTIC= UUID.fromString("00001524-1212-efde-1523-785feabcd123");
// public static UUID BATTERY_SERVICE = UUID.fromString("180f");
// public static UUID BATTERY_LEVEL = UUID.fromString("2a19");
public static List<UUID> getDesiredCharacteristics() {
return Arrays.asList(KRAKEN_BUTTONS_CHARACTERISTIC, KRAKEN_SENSORS_CHARACTERISTIC);
}
}
......@@ -179,6 +179,10 @@ public class DrawPreview {
private final float [] gyro_direction_up = new float[3];
private final float [] transformed_gyro_direction_up = new float[3];
// OSD extra lines
private String OSDLine1;
private String OSDLine2;
public DrawPreview(MainActivity main_activity, MyApplicationInterface applicationInterface) {
if( MyDebug.LOG )
Log.d(TAG, "DrawPreview");
......@@ -859,7 +863,7 @@ public class DrawPreview {
}
}
private void onDrawInfoLines(Canvas canvas, final int top_x, final int top_y, long time_ms) {
private void onDrawInfoLines(Canvas canvas, final int top_x, final int top_y, final int bottom_y, long time_ms) {
Preview preview = main_activity.getPreview();
CameraController camera_controller = preview.getCameraController();
int ui_rotation = preview.getUIRotation();
......@@ -955,6 +959,21 @@ public class DrawPreview {
}
}
// Now draw additional info on the lower left corner if needed
int y_offset = (int) (27 * scale + 0.5f);
p.setTextSize(24 * scale + 0.5f); // convert dps to pixels
if (OSDLine1 != null && OSDLine1.length() > 0) {
applicationInterface.drawTextWithBackground(canvas, p, OSDLine1,
Color.WHITE, Color.BLACK, location_x, bottom_y - y_offset,
MyApplicationInterface.Alignment.ALIGNMENT_BOTTOM, null, true);
}
if (OSDLine2 != null && OSDLine2.length() > 0) {
applicationInterface.drawTextWithBackground(canvas, p, OSDLine2,
Color.WHITE, Color.BLACK, location_x, bottom_y,
MyApplicationInterface.Alignment.ALIGNMENT_BOTTOM, null, true);
}
p.setTextSize(16 * scale + 0.5f); // Restore text size
if( camera_controller != null && show_iso_pref ) {
if( iso_exposure_string == null || time_ms > last_iso_exposure_time + 500 ) {
iso_exposure_string = "";
......@@ -1274,6 +1293,7 @@ public class DrawPreview {
double level_angle = preview.getLevelAngle();
boolean has_geo_direction = preview.hasGeoDirection();
double geo_direction = preview.getGeoDirection();
int text_base_y = 0;
canvas.save();
canvas.rotate(ui_rotation, canvas.getWidth()/2.0f, canvas.getHeight()/2.0f);
......@@ -1283,7 +1303,6 @@ public class DrawPreview {
canvas.getHeight() / 2, p);*/
int text_y = (int) (20 * scale + 0.5f); // convert dps to pixels
// fine tuning to adjust placement of text with respect to the GUI, depending on orientation
int text_base_y = 0;
if( ui_placement == MainUI.UIPlacement.UIPLACEMENT_TOP && ( ui_rotation == 0 || ui_rotation == 180 ) ) {
text_base_y = canvas.getHeight() - (int)(0.5*text_y);
}
......@@ -1632,7 +1651,7 @@ public class DrawPreview {
top_x += (int) (10 * scale + 0.5f); // convert dps to pixels
}
onDrawInfoLines(canvas, top_x, top_y, time_ms);
onDrawInfoLines(canvas, top_x, top_y, text_base_y, time_ms);
canvas.restore();
}
......@@ -2202,6 +2221,21 @@ public class DrawPreview {
canvas.drawLine(cx - radius*dir_y, cy + radius*dir_x, cx + radius*dir_y, cy - radius*dir_x, p);
}
/**
* A generic method to display up to two lines on the preview.
* Currently used by the Kraken underwater housing sensor to display
* temperature and depth.
*
* The two lines are displayed in the lower left corner of the screen.
*
* @param line1 First line to display
* @param line2 Second line to display
*/
public void onExtraOSDValuesChanged(String line1, String line2) {
OSDLine1 = line1;
OSDLine2 = line2;
}
// for testing:
public boolean getStoredHasStampPref() {
......
<vector android:height="24dp" android:viewportHeight="133.50002"
android:viewportWidth="133.50002" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillAlpha="1" android:fillColor="#7b7b7b"
android:pathData="M12.233,30.929L121.267,30.929A9.233,9.233 0,0 1,130.5 40.162L130.5,95.267A9.233,9.233 0,0 1,121.267 104.5L12.233,104.5A9.233,9.233 0,0 1,3 95.267L3,40.162A9.233,9.233 0,0 1,12.233 30.929z"
android:strokeAlpha="1" android:strokeColor="#000000" android:strokeWidth="6"/>
<path android:fillAlpha="0.97452231" android:fillColor="#f2f2f2"
android:pathData="M100.804,40.214L112.696,40.214A9.233,9.233 0,0 1,121.929 49.447L121.929,85.267A9.233,9.233 0,0 1,112.696 94.5L100.804,94.5A9.233,9.233 0,0 1,91.571 85.267L91.571,49.447A9.233,9.233 0,0 1,100.804 40.214z"
android:strokeAlpha="1" android:strokeColor="#000000" android:strokeWidth="6"/>
<path android:fillAlpha="1" android:fillColor="#f2f2f2"
android:pathData="M57.821,44.143L62.107,44.143A7.679,7.679 0,0 1,69.786 51.821L69.786,72.536A7.679,7.679 0,0 1,62.107 80.214L57.821,80.214A7.679,7.679 0,0 1,50.143 72.536L50.143,51.821A7.679,7.679 0,0 1,57.821 44.143z"
android:strokeAlpha="1" android:strokeColor="#000000" android:strokeWidth="2"/>
<path android:fillColor="#00000000" android:fillType="evenOdd"
android:pathData="m11.929,38.786 l14.286,0"
android:strokeAlpha="1" android:strokeColor="#000000"
android:strokeLineCap="round" android:strokeLineJoin="miter" android:strokeWidth="6"/>
<path android:fillAlpha="1" android:fillColor="#00000000"
android:pathData="m21.246,46.107a7.321,7.321 0,0 0,7.318 7.321,7.321 7.321,0 0,0 7.325,-7.314 7.321,7.321 0,0 0,-7.31 -7.329,7.321 7.321,0 0,0 -7.333,7.306"
android:strokeAlpha="1" android:strokeColor="#000000" android:strokeWidth="6"/>
<path android:fillAlpha="1" android:fillColor="#7b7b7b"
android:pathData="M12.233,59.5L22.338,59.5A9.233,9.233 0,0 1,31.571 68.733L31.571,95.267A9.233,9.233 0,0 1,22.338 104.5L12.233,104.5A9.233,9.233 0,0 1,3 95.267L3,68.733A9.233,9.233 0,0 1,12.233 59.5z"
android:strokeAlpha="1" android:strokeColor="#000000"
android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="6"/>
<path android:fillAlpha="1" android:fillColor="#7ecee4"
android:pathData="m53.886,70.671a5.801,5.801 0,0 0,5.798 5.801,5.801 5.801,0 0,0 5.804,-5.795 5.801,5.801 0,0 0,-5.792 -5.807,5.801 5.801,0 0,0 -5.811,5.789"
android:strokeAlpha="1" android:strokeColor="#000000" android:strokeWidth="2"/>
<path android:fillAlpha="1" android:fillColor="#7ecee4"
android:pathData="m53.886,54.671a5.801,5.801 0,0 0,5.798 5.801,5.801 5.801,0 0,0 5.804,-5.795 5.801,5.801 0,0 0,-5.792 -5.807,5.801 5.801,0 0,0 -5.811,5.789"
android:strokeAlpha="1" android:strokeColor="#000000" android:strokeWidth="2"/>
<path android:fillAlpha="1" android:fillColor="#000000"
android:pathData="m55.561,54.671a4.126,4.126 0,0 0,4.124 4.126,4.126 4.126,0 0,0 4.128,-4.122 4.126,4.126 0,0 0,-4.119 -4.13,4.126 4.126,0 0,0 -4.132,4.117"
android:strokeAlpha="1" android:strokeColor="#7ecee4" android:strokeWidth="1.42238855"/>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingTop="0dp"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".Remotecontrol.DeviceScanner">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/currentRemote"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Current remote: " />
<Button
android:id="@+id/StartScanButton"
android:layout_width="100dp"
android:layout_height="50dp"
android:layout_gravity="right"
android:layout_marginTop="0dp"
android:text="Scan" />
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:drawSelectorOnTop="false">
</ListView>
</LinearLayout>
</RelativeLayout>
\ No newline at end of file
......@@ -209,6 +209,20 @@
android:visibility="gone"
/>
<ImageButton
android:id="@+id/kraken_icon"
android:layout_width="@dimen/onscreen_button_size"
android:layout_height="@dimen/onscreen_button_size"
android:padding="10dp"
android:scaleType="fitCenter"
android:contentDescription="@string/remote_connected"
android:src="@drawable/ic_kraken_icon"
android:background="@color/icons_background"
android:backgroundTint="@color/icons_background_tint"
android:backgroundTintMode="src_in"
/>
<ImageButton
android:id="@+id/white_balance_lock"
android:layout_width="@dimen/onscreen_button_size"
......
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView android:id="@+id/device_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="24dp"/>
<TextView android:id="@+id/device_address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="12dp"/>
</LinearLayout>
......@@ -851,4 +851,10 @@
<item>ui_right</item>
<item>ui_top</item>
</string-array>
<string-array name="preference_remote_type_entries">
<item>@string/preference_remote_type_kraken</item>
</string-array>
<string-array name="preference_remote_type_values">
<item>preference_remote_type_kraken</item>
</string-array>
</resources>
......@@ -782,6 +782,23 @@
<string name="photo_mode_panorama">Pano</string> <!-- keep short, as this shows in the popup menu; probably shouldn't be translated? -->
<string name="photo_mode_panorama_full">Panorama</string>
<string name="preference_screen_remote_control">Remote control...</string>
<string name="preference_enable_remote">Enable remote control</string>
<string name="preference_enable_remote_summary">Enable BLE remote control devices</string>
<string name="preference_remote_type">Remote device type</string>
<string name="ble_not_supported">BLE not supported</string>
<string name="scan_ble">BLE Device Scan</string>
<string name="bluetooth_not_supported">Bluetooth not supported.</string>
<string name="unknown_device">Unknown device</string>
<string name="preference_remote_disconnect_screen_dim">Dim screen if remote disconnects</string>
<string name="preference_remote_disconnect_screen_dim_summary">Hint: set default brightness to minimum level before starting Open Camera.</string>
<string name="preference_water_type">Use Saltwater for depth calculations</string>
<string name="preference_water_type_summary">For underwater housings, improves accuracy if correct water type is selected.</string>
<string name="remote_connected">Remote connected</string>
<string name="preference_remote_type_kraken">Kraken Smart Housing</string>
<!-- There's no point translating the What's New text - it'll be updated constantly, and change each version -->
<string name="whats_new_text">
[This dialog is shown when Open Camera is updated. You can disable it under
......
......@@ -267,6 +267,50 @@
</PreferenceScreen>
<PreferenceScreen
android:key="preference_screen_remote_control"
android:title="@string/preference_screen_remote_control"
android:icon="@drawable/ic_more_horiz_white_48dp"
android:persistent="false">
<SwitchPreference
android:key="preference_enable_remote"
android:title="@string/preference_enable_remote"
android:summary="@string/preference_enable_remote_summary"
android:defaultValue="false"
/>
<ListPreference
android:key="preference_remote_type"
android:title="@string/preference_remote_type"
android:summary="%s"
android:entries="@array/preference_remote_type_entries"
android:entryValues="@array/preference_remote_type_values"
android:defaultValue="preference_remote_type_kraken"
/>
<PreferenceScreen
android:key="preference_remote_device_name"
android:title="Select remote device" >
<intent android:action="net.sourceforge.opencamera.Remotecontrol.DeviceScanner"/>
</PreferenceScreen>
<SwitchPreference
android:key="preference_remote_disconnect_screen_dim"
android:title="@string/preference_remote_disconnect_screen_dim"
android:summary="@string/preference_remote_disconnect_screen_dim_summary"
android:defaultValue="false"
/>
<SwitchPreference
android:key="preference_water_type"
android:title="@string/preference_water_type"
android:summary="@string/preference_water_type_summary"
android:defaultValue="true"
/>
</PreferenceScreen>
<PreferenceScreen
android:key="preference_screen_gui"
android:title="@string/preference_screen_gui"
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment