mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
First checkin of actual code
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 21
|
||||
buildToolsVersion "21.1.2"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "nodomain.freeyourgadget.gadgetbridge"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 21
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
compile 'com.android.support:appcompat-v7:21.0.3'
|
||||
compile 'com.android.support:support-v4:21.0.3'
|
||||
}
|
||||
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /home/andi/Android/Sdk/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
@@ -0,0 +1,13 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge;
|
||||
|
||||
import android.app.Application;
|
||||
import android.test.ApplicationTestCase;
|
||||
|
||||
/**
|
||||
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
|
||||
*/
|
||||
public class ApplicationTest extends ApplicationTestCase<Application> {
|
||||
public ApplicationTest() {
|
||||
super(Application.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="nodomain.freeyourgadget.gadgetbridge">
|
||||
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
|
||||
<application
|
||||
|
||||
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:name="nodomain.freeyourgadget.gadgetbridge.SettingsActivity"
|
||||
android:label="@string/app_name"></activity>
|
||||
<activity
|
||||
android:name="nodomain.freeyourgadget.gadgetbridge.ControlCenter"
|
||||
android:label="@string/title_activity_main">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name="nodomain.freeyourgadget.gadgetbridge.NotificationListener"
|
||||
android:label="@string/app_name"
|
||||
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.notification.NotificationListenerService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,236 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothSocket;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.SystemClock;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ControlCenter extends ActionBarActivity {
|
||||
// SPP Serial Device UUID
|
||||
private static final UUID SERIAL_UUID = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb");
|
||||
|
||||
BluetoothAdapter mBtAdapter;
|
||||
BluetoothDevice mBtDevice;
|
||||
BluetoothSocket mBtSocket;
|
||||
Button sendButton;
|
||||
Button testNotificationButton;
|
||||
EditText editTitle;
|
||||
EditText editContent;
|
||||
private NotificationReceiver nReceiver;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_controlcenter);
|
||||
|
||||
//Check the system status
|
||||
mBtAdapter = BluetoothAdapter.getDefaultAdapter();
|
||||
if (mBtAdapter == null) {
|
||||
Toast.makeText(this, "Bluetooth is not supported.", Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
if (!mBtAdapter.isEnabled()) {
|
||||
Toast.makeText(this, "Bluetooth is disabled.", Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
Set<BluetoothDevice> pairedDevices = mBtAdapter.getBondedDevices();
|
||||
for (BluetoothDevice device : pairedDevices) {
|
||||
if (device.getName().indexOf("Pebble") == 0) {
|
||||
// Matching device found
|
||||
mBtDevice = device;
|
||||
}
|
||||
}
|
||||
|
||||
editTitle = (EditText) findViewById(R.id.editTitle);
|
||||
editContent = (EditText) findViewById(R.id.editContent);
|
||||
sendButton = (Button) findViewById(R.id.sendButton);
|
||||
sendButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
||||
if (!mBtAdapter.isEnabled() || mBtDevice == null)
|
||||
return;
|
||||
String title = editTitle.getText().toString();
|
||||
String content = editContent.getText().toString();
|
||||
try {
|
||||
if (mBtSocket == null || !mBtSocket.isConnected()) {
|
||||
mBtSocket = mBtDevice.createRfcommSocketToServiceRecord(SERIAL_UUID);
|
||||
mBtSocket.connect();
|
||||
}
|
||||
ConnectedTask task = new ConnectedTask();
|
||||
task.execute(mBtSocket, title, content);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
testNotificationButton = (Button) findViewById(R.id.testNotificationButton);
|
||||
testNotificationButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
testNotification();
|
||||
}
|
||||
});
|
||||
|
||||
Intent enableIntent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
|
||||
|
||||
startActivity(enableIntent);
|
||||
nReceiver = new NotificationReceiver();
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction("nodomain.freeyourgadget.gadgetbridge.NOTIFICATION_LISTENER");
|
||||
registerReceiver(nReceiver, filter);
|
||||
}
|
||||
|
||||
private void testNotification() {
|
||||
NotificationManager nManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
NotificationCompat.Builder ncomp = new NotificationCompat.Builder(this);
|
||||
ncomp.setContentTitle("Test Notification");
|
||||
ncomp.setContentText("This is a Test Notification from Gadgetbridge");
|
||||
ncomp.setTicker("This is a Test Notificytion from Gadgetbridge");
|
||||
ncomp.setSmallIcon(R.drawable.ic_launcher);
|
||||
ncomp.setAutoCancel(true);
|
||||
nManager.notify((int) System.currentTimeMillis(), ncomp.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
// Inflate the menu; this adds items to the action bar if it is present.
|
||||
getMenuInflater().inflate(R.menu.menu_main, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// Handle action bar item clicks here. The action bar will
|
||||
// automatically handle clicks on the Home/Up button, so long
|
||||
// as you specify a parent activity in AndroidManifest.xml.
|
||||
int id = item.getItemId();
|
||||
|
||||
//noinspection SimplifiableIfStatement
|
||||
if (id == R.id.action_settings) {
|
||||
//Intent intent = new Intent(this, SettingsActivity.class);
|
||||
//startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
try {
|
||||
if (mBtSocket != null) {
|
||||
mBtSocket.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (nReceiver != null) {
|
||||
unregisterReceiver(nReceiver);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//AsyncTask to receive a single line of data and post
|
||||
private class ConnectedTask extends
|
||||
AsyncTask<Object, Void, String> {
|
||||
@Override
|
||||
protected String doInBackground(
|
||||
Object... params) {
|
||||
InputStream in = null;
|
||||
OutputStream out = null;
|
||||
BluetoothSocket socket = (BluetoothSocket) params[0];
|
||||
String title = (String) params[1];
|
||||
String content = (String) params[2];
|
||||
try {
|
||||
byte[] buffer = new byte[1024];
|
||||
String result;
|
||||
in = socket.getInputStream();
|
||||
|
||||
//in.read(buffer);
|
||||
//result = PebbleProtocol.decodeResponse(buffer);
|
||||
|
||||
out = socket.getOutputStream();
|
||||
|
||||
|
||||
byte[] msg;
|
||||
msg = PebbleProtocol.encodeSMS(title, content);
|
||||
|
||||
//msg = PebbleProtocol.encodeSetTime();
|
||||
//msg = PebbleProtocol.encodeIncomingCall("03012323", title);
|
||||
//msg = PebbleProtocol.encodeEmail(title, "subject", content);
|
||||
|
||||
out.write(msg);
|
||||
SystemClock.sleep(500);
|
||||
//in.read(buffer);
|
||||
//result = PebbleProtocol.decodeResponse(buffer);
|
||||
result = "ok";
|
||||
//Close the connection
|
||||
return result.trim();
|
||||
} catch (Exception exc) {
|
||||
return "error";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(String result) {
|
||||
Toast.makeText(ControlCenter.this, result,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
try {
|
||||
mBtSocket.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class NotificationReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (!mBtAdapter.isEnabled() || mBtDevice == null)
|
||||
return;
|
||||
|
||||
String title = intent.getStringExtra("notification_title");
|
||||
String content = intent.getStringExtra("notification_content");
|
||||
try {
|
||||
if (mBtSocket == null || !mBtSocket.isConnected()) {
|
||||
mBtSocket = mBtDevice.createRfcommSocketToServiceRecord(SERIAL_UUID);
|
||||
mBtSocket.connect();
|
||||
}
|
||||
ConnectedTask task = new ConnectedTask();
|
||||
task.execute(mBtSocket, title, content);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.service.notification.NotificationListenerService;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
|
||||
public class NotificationListener extends NotificationListenerService {
|
||||
|
||||
private String TAG = this.getClass().getSimpleName();
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificationPosted(StatusBarNotification sbn) {
|
||||
Intent i = new Intent("nodomain.freeyourgadget.gadgetbridge.NOTIFICATION_LISTENER");
|
||||
Notification notification = sbn.getNotification();
|
||||
Bundle extras = notification.extras;
|
||||
String title = extras.getCharSequence(Notification.EXTRA_TITLE).toString();
|
||||
String content = extras.getCharSequence(Notification.EXTRA_TEXT).toString();
|
||||
i.putExtra("notification_title", title);
|
||||
i.putExtra("notification_content", content);
|
||||
sendBroadcast(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificationRemoved(StatusBarNotification sbn) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.SimpleTimeZone;
|
||||
|
||||
public class PebbleProtocol {
|
||||
static final short ENDPOINT_FIRMWARE = 1;
|
||||
static final short ENDPOINT_TIME = 11;
|
||||
static final short ENDPOINT_FIRMWAREVERSION = 16;
|
||||
static final short ENDPOINT_PHONEVERSION = 17;
|
||||
static final short ENDPOINT_SYSTEMMESSAGE = 18;
|
||||
static final short ENDPOINT_MUSICCONTROL = 32;
|
||||
static final short ENDPOINT_PHONECONTROL = 33;
|
||||
static final short ENDPOINT_APPLICATIONMESSAGE = 48;
|
||||
static final short ENDPOINT_LAUNCHER = 49;
|
||||
static final short ENDPOINT_LOGS = 2000;
|
||||
static final short ENDPOINT_PING = 2001;
|
||||
static final short ENDPOINT_LOGDUMP = 2002;
|
||||
static final short ENDPOINT_RESET = 2003;
|
||||
static final short ENDPOINT_APP = 2004;
|
||||
static final short ENDPOINT_APPLOGS = 2006;
|
||||
static final short ENDPOINT_NOTIFICATION = 3000;
|
||||
static final short ENDPOINT_RESOURCE = 4000;
|
||||
static final short ENDPOINT_SYSREG = 5000;
|
||||
static final short ENDPOINT_FCTREG = 5001;
|
||||
static final short ENDPOINT_APPMANAGER = 6000;
|
||||
static final short ENDPOINT_RUNKEEPER = 7000;
|
||||
static final short ENDPOINT_SCREENSHOT = 8000;
|
||||
static final short ENDPOINT_PUTBYTES = (short) 48879;
|
||||
|
||||
static final byte NOTIFICATION_EMAIL = 0;
|
||||
static final byte NOTIFICATION_SMS = 1;
|
||||
static final byte NOTIFICATION_TWITTER = 2;
|
||||
static final byte NOTIFICATION_FACEBOOK = 3;
|
||||
|
||||
static final byte PHONECONTROL_ANSWER = 1;
|
||||
static final byte PHONECONTROL_HANGUP = 2;
|
||||
static final byte PHONECONTROL_GETSTATE = 3;
|
||||
static final byte PHONECONTROL_INCOMINGCALL = 4;
|
||||
static final byte PHONECONTROL_OUTGOINGCALL = 5;
|
||||
static final byte PHONECONTROL_MISSEDCALL = 6;
|
||||
static final byte PHONECONTROL_RING = 7;
|
||||
static final byte PHONECONTROL_START = 8;
|
||||
static final byte PHONECONTROL_END = 9;
|
||||
|
||||
static final byte TIME_GETTIME = 0;
|
||||
static final byte TIME_SETTIME = 2;
|
||||
|
||||
static final byte LENGTH_PREFIX = 4;
|
||||
static final byte LENGTH_SETTIME = 9;
|
||||
|
||||
static byte[] encodeMessage(short endpoint, byte type, String[] parts) {
|
||||
// Calculate length first
|
||||
int length = LENGTH_PREFIX + 1;
|
||||
for (String s : parts) {
|
||||
length += (1 + s.getBytes().length);
|
||||
}
|
||||
|
||||
// Encode Prefix
|
||||
ByteBuffer buf = ByteBuffer.allocate(length);
|
||||
buf.order(ByteOrder.BIG_ENDIAN);
|
||||
buf.putShort((short) (length - LENGTH_PREFIX));
|
||||
buf.putShort(endpoint);
|
||||
buf.put(type);
|
||||
|
||||
// Encode Pascal-Style Strings
|
||||
for (String s : parts) {
|
||||
|
||||
int partlength = s.getBytes().length;
|
||||
if (partlength > 255) partlength = 255;
|
||||
buf.put((byte) partlength);
|
||||
buf.put(s.getBytes(), 0, partlength);
|
||||
}
|
||||
|
||||
return buf.array();
|
||||
}
|
||||
|
||||
public static byte[] encodeSMS(String from, String body) {
|
||||
Long ts = System.currentTimeMillis() / 1000;
|
||||
String tsstring = ts.toString(); // SIC
|
||||
String[] parts = {from, body, tsstring};
|
||||
|
||||
return encodeMessage(ENDPOINT_NOTIFICATION, NOTIFICATION_SMS, parts);
|
||||
}
|
||||
|
||||
public static byte[] encodeEmail(String from, String subject, String body) {
|
||||
Long ts = System.currentTimeMillis() / 1000;
|
||||
String tsstring = ts.toString(); // SIC
|
||||
String[] parts = {from, body, tsstring, subject};
|
||||
|
||||
return encodeMessage(ENDPOINT_NOTIFICATION, NOTIFICATION_EMAIL, parts);
|
||||
}
|
||||
|
||||
public static byte[] encodeSetTime() {
|
||||
long ts = System.currentTimeMillis() / 1000;
|
||||
ts += SimpleTimeZone.getDefault().getOffset(ts) / 1000;
|
||||
ByteBuffer buf = ByteBuffer.allocate(LENGTH_SETTIME);
|
||||
buf.order(ByteOrder.BIG_ENDIAN);
|
||||
buf.putShort((short) (LENGTH_SETTIME - LENGTH_PREFIX));
|
||||
buf.putShort(ENDPOINT_TIME);
|
||||
buf.put(TIME_SETTIME);
|
||||
buf.putInt((int) ts);
|
||||
|
||||
return buf.array();
|
||||
}
|
||||
|
||||
public static byte[] encodeIncomingCall(String number, String name) {
|
||||
String cookie = "000"; // That's a dirty trick to make the cookie part 4 bytes long :P
|
||||
String[] parts = {cookie, number, name};
|
||||
return encodeMessage(ENDPOINT_PHONECONTROL, PHONECONTROL_INCOMINGCALL, parts);
|
||||
}
|
||||
|
||||
// FIXME: that should return data into some unified struct/Class
|
||||
public static String decodeResponse(byte[] responseData) {
|
||||
ByteBuffer buf = ByteBuffer.wrap(responseData);
|
||||
buf.order(ByteOrder.BIG_ENDIAN);
|
||||
short length = buf.getShort();
|
||||
short endpoint = buf.getShort();
|
||||
byte extra = 0;
|
||||
|
||||
switch (endpoint) {
|
||||
case ENDPOINT_PHONECONTROL:
|
||||
extra = buf.get();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
String ret = Short.toString(length) + "/" + Short.toString(endpoint) + "/" + Byte.toString(extra);
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.media.Ringtone;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.preference.RingtonePreference;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A {@link PreferenceActivity} that presents a set of application settings. On
|
||||
* handset devices, settings are presented as a single list. On tablets,
|
||||
* settings are split by category, with category headers shown to the left of
|
||||
* the list of settings.
|
||||
* <p/>
|
||||
* See <a href="http://developer.android.com/design/patterns/settings.html">
|
||||
* Android Design: Settings</a> for design guidelines and the <a
|
||||
* href="http://developer.android.com/guide/topics/ui/settings.html">Settings
|
||||
* API Guide</a> for more information on developing a Settings UI.
|
||||
*/
|
||||
public class SettingsActivity extends PreferenceActivity {
|
||||
/**
|
||||
* Determines whether to always show the simplified settings UI, where
|
||||
* settings are presented in a single list. When false, settings are shown
|
||||
* as a master/detail two-pane view on tablets. When true, a single pane is
|
||||
* shown on tablets.
|
||||
*/
|
||||
private static final boolean ALWAYS_SIMPLE_PREFS = false;
|
||||
|
||||
|
||||
@Override
|
||||
protected void onPostCreate(Bundle savedInstanceState) {
|
||||
super.onPostCreate(savedInstanceState);
|
||||
|
||||
setupSimplePreferencesScreen();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the simplified settings UI if the device configuration if the
|
||||
* device configuration dictates that a simplified, single-pane UI should be
|
||||
* shown.
|
||||
*/
|
||||
private void setupSimplePreferencesScreen() {
|
||||
if (!isSimplePreferences(this)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// In the simplified UI, fragments are not used at all and we instead
|
||||
// use the older PreferenceActivity APIs.
|
||||
|
||||
// Add 'general' preferences.
|
||||
addPreferencesFromResource(R.xml.pref_general);
|
||||
|
||||
// Add 'notifications' preferences, and a corresponding header.
|
||||
PreferenceCategory fakeHeader = new PreferenceCategory(this);
|
||||
fakeHeader.setTitle(R.string.pref_header_notifications);
|
||||
getPreferenceScreen().addPreference(fakeHeader);
|
||||
addPreferencesFromResource(R.xml.pref_notification);
|
||||
|
||||
// Add 'data and sync' preferences, and a corresponding header.
|
||||
fakeHeader = new PreferenceCategory(this);
|
||||
fakeHeader.setTitle(R.string.pref_header_data_sync);
|
||||
getPreferenceScreen().addPreference(fakeHeader);
|
||||
addPreferencesFromResource(R.xml.pref_data_sync);
|
||||
|
||||
// Bind the summaries of EditText/List/Dialog/Ringtone preferences to
|
||||
// their values. When their values change, their summaries are updated
|
||||
// to reflect the new value, per the Android Design guidelines.
|
||||
bindPreferenceSummaryToValue(findPreference("example_text"));
|
||||
bindPreferenceSummaryToValue(findPreference("example_list"));
|
||||
bindPreferenceSummaryToValue(findPreference("notifications_new_message_ringtone"));
|
||||
bindPreferenceSummaryToValue(findPreference("sync_frequency"));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean onIsMultiPane() {
|
||||
return isXLargeTablet(this) && !isSimplePreferences(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to determine if the device has an extra-large screen. For
|
||||
* example, 10" tablets are extra-large.
|
||||
*/
|
||||
private static boolean isXLargeTablet(Context context) {
|
||||
return (context.getResources().getConfiguration().screenLayout
|
||||
& Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the simplified settings UI should be shown. This is
|
||||
* true if this is forced via {@link #ALWAYS_SIMPLE_PREFS}, or the device
|
||||
* doesn't have newer APIs like {@link PreferenceFragment}, or the device
|
||||
* doesn't have an extra-large screen. In these cases, a single-pane
|
||||
* "simplified" settings UI should be shown.
|
||||
*/
|
||||
private static boolean isSimplePreferences(Context context) {
|
||||
return ALWAYS_SIMPLE_PREFS
|
||||
|| Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB
|
||||
|| !isXLargeTablet(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
public void onBuildHeaders(List<Header> target) {
|
||||
if (!isSimplePreferences(this)) {
|
||||
loadHeadersFromResource(R.xml.pref_headers, target);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A preference value change listener that updates the preference's summary
|
||||
* to reflect its new value.
|
||||
*/
|
||||
private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object value) {
|
||||
String stringValue = value.toString();
|
||||
|
||||
if (preference instanceof ListPreference) {
|
||||
// For list preferences, look up the correct display value in
|
||||
// the preference's 'entries' list.
|
||||
ListPreference listPreference = (ListPreference) preference;
|
||||
int index = listPreference.findIndexOfValue(stringValue);
|
||||
|
||||
// Set the summary to reflect the new value.
|
||||
preference.setSummary(
|
||||
index >= 0
|
||||
? listPreference.getEntries()[index]
|
||||
: null);
|
||||
|
||||
} else if (preference instanceof RingtonePreference) {
|
||||
// For ringtone preferences, look up the correct display value
|
||||
// using RingtoneManager.
|
||||
if (TextUtils.isEmpty(stringValue)) {
|
||||
// Empty values correspond to 'silent' (no ringtone).
|
||||
preference.setSummary(R.string.pref_ringtone_silent);
|
||||
|
||||
} else {
|
||||
Ringtone ringtone = RingtoneManager.getRingtone(
|
||||
preference.getContext(), Uri.parse(stringValue));
|
||||
|
||||
if (ringtone == null) {
|
||||
// Clear the summary if there was a lookup error.
|
||||
preference.setSummary(null);
|
||||
} else {
|
||||
// Set the summary to reflect the new ringtone display
|
||||
// name.
|
||||
String name = ringtone.getTitle(preference.getContext());
|
||||
preference.setSummary(name);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// For all other preferences, set the summary to the value's
|
||||
// simple string representation.
|
||||
preference.setSummary(stringValue);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Binds a preference's summary to its value. More specifically, when the
|
||||
* preference's value is changed, its summary (line of text below the
|
||||
* preference title) is updated to reflect the value. The summary is also
|
||||
* immediately updated upon calling this method. The exact display format is
|
||||
* dependent on the type of preference.
|
||||
*
|
||||
* @see #sBindPreferenceSummaryToValueListener
|
||||
*/
|
||||
private static void bindPreferenceSummaryToValue(Preference preference) {
|
||||
// Set the listener to watch for value changes.
|
||||
preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener);
|
||||
|
||||
// Trigger the listener immediately with the preference's
|
||||
// current value.
|
||||
sBindPreferenceSummaryToValueListener.onPreferenceChange(preference,
|
||||
PreferenceManager
|
||||
.getDefaultSharedPreferences(preference.getContext())
|
||||
.getString(preference.getKey(), ""));
|
||||
}
|
||||
|
||||
/**
|
||||
* This fragment shows general preferences only. It is used when the
|
||||
* activity is showing a two-pane settings UI.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
public static class GeneralPreferenceFragment extends PreferenceFragment {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
addPreferencesFromResource(R.xml.pref_general);
|
||||
|
||||
// Bind the summaries of EditText/List/Dialog/Ringtone preferences
|
||||
// to their values. When their values change, their summaries are
|
||||
// updated to reflect the new value, per the Android Design
|
||||
// guidelines.
|
||||
bindPreferenceSummaryToValue(findPreference("example_text"));
|
||||
bindPreferenceSummaryToValue(findPreference("example_list"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This fragment shows notification preferences only. It is used when the
|
||||
* activity is showing a two-pane settings UI.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
public static class NotificationPreferenceFragment extends PreferenceFragment {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
addPreferencesFromResource(R.xml.pref_notification);
|
||||
|
||||
// Bind the summaries of EditText/List/Dialog/Ringtone preferences
|
||||
// to their values. When their values change, their summaries are
|
||||
// updated to reflect the new value, per the Android Design
|
||||
// guidelines.
|
||||
bindPreferenceSummaryToValue(findPreference("notifications_new_message_ringtone"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This fragment shows data and sync preferences only. It is used when the
|
||||
* activity is showing a two-pane settings UI.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
public static class DataSyncPreferenceFragment extends PreferenceFragment {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
addPreferencesFromResource(R.xml.pref_data_sync);
|
||||
|
||||
// Bind the summaries of EditText/List/Dialog/Ringtone preferences
|
||||
// to their values. When their values change, their summaries are
|
||||
// updated to reflect the new value, per the Android Design
|
||||
// guidelines.
|
||||
bindPreferenceSummaryToValue(findPreference("sync_frequency"));
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
@@ -0,0 +1,54 @@
|
||||
<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:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.ControlCenter">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:text="From:" />
|
||||
<EditText
|
||||
android:id="@+id/editTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/label"
|
||||
android:singleLine="true"
|
||||
android:inputType="textEmailAddress" />
|
||||
<TextView
|
||||
android:id="@+id/textView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:layout_below="@+id/editTitle"
|
||||
android:text="Message"/>
|
||||
<EditText
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textMultiLine"
|
||||
android:ems="10"
|
||||
android:id="@+id/editContent"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignRight="@+id/editTitle"
|
||||
android:layout_alignEnd="@+id/editTitle"
|
||||
android:layout_below="@+id/textView" />
|
||||
<Button
|
||||
android:id="@+id/sendButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Send to Pebble"
|
||||
android:layout_above="@+id/testNotificationButton"
|
||||
android:layout_alignParentStart="true" />
|
||||
<Button
|
||||
android:id="@+id/testNotificationButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Create test Notification"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentStart="true" />
|
||||
</RelativeLayout>
|
||||
@@ -0,0 +1,7 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.ControlCenter">
|
||||
<item android:id="@+id/action_settings" android:title="@string/action_settings"
|
||||
android:orderInCategory="100" app:showAsAction="never" />
|
||||
</menu>
|
||||
@@ -0,0 +1,6 @@
|
||||
<resources>
|
||||
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
|
||||
(such as screen margins) for screens with more than 820dp of available width. This
|
||||
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
|
||||
<dimen name="activity_horizontal_margin">64dp</dimen>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<resources>
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
</resources>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="app_name">Gadgetbridge</string>
|
||||
<string name="title_activity_main">Gadgetbridge Control Center</string>
|
||||
<string name="hello_world">Hello world!</string>
|
||||
<string name="action_settings">Settings</string>
|
||||
|
||||
</resources>
|
||||
@@ -0,0 +1,60 @@
|
||||
<resources>
|
||||
|
||||
<!-- Strings related to Settings -->
|
||||
|
||||
<!-- Example General settings -->
|
||||
<string name="pref_header_general">General</string>
|
||||
|
||||
<string name="pref_title_social_recommendations">Enable social recommendations</string>
|
||||
<string name="pref_description_social_recommendations">Recommendations for people to contact
|
||||
based on your message history
|
||||
</string>
|
||||
|
||||
<string name="pref_title_display_name">Display name</string>
|
||||
<string name="pref_default_display_name">John Smith</string>
|
||||
|
||||
<string name="pref_title_add_friends_to_messages">Add friends to messages</string>
|
||||
<string-array name="pref_example_list_titles">
|
||||
<item>Always</item>
|
||||
<item>When possible</item>
|
||||
<item>Never</item>
|
||||
</string-array>
|
||||
<string-array name="pref_example_list_values">
|
||||
<item>1</item>
|
||||
<item>0</item>
|
||||
<item>-1</item>
|
||||
</string-array>
|
||||
|
||||
<!-- Example settings for Data & Sync -->
|
||||
<string name="pref_header_data_sync">Data & sync</string>
|
||||
|
||||
<string name="pref_title_sync_frequency">Sync frequency</string>
|
||||
<string-array name="pref_sync_frequency_titles">
|
||||
<item>15 minutes</item>
|
||||
<item>30 minutes</item>
|
||||
<item>1 hour</item>
|
||||
<item>3 hours</item>
|
||||
<item>6 hours</item>
|
||||
<item>Never</item>
|
||||
</string-array>
|
||||
<string-array name="pref_sync_frequency_values">
|
||||
<item>15</item>
|
||||
<item>30</item>
|
||||
<item>60</item>
|
||||
<item>180</item>
|
||||
<item>360</item>
|
||||
<item>-1</item>
|
||||
</string-array>
|
||||
|
||||
<string name="pref_title_system_sync_settings">System sync settings</string>
|
||||
|
||||
<!-- Example settings for Notifications -->
|
||||
<string name="pref_header_notifications">Notifications</string>
|
||||
|
||||
<string name="pref_title_new_message_notifications">New message notifications</string>
|
||||
|
||||
<string name="pref_title_ringtone">Ringtone</string>
|
||||
<string name="pref_ringtone_silent">Silent</string>
|
||||
|
||||
<string name="pref_title_vibrate">Vibrate</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,8 @@
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
@@ -0,0 +1,21 @@
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- NOTE: Hide buttons to simplify the UI. Users can touch outside the dialog to
|
||||
dismiss it. -->
|
||||
<!-- NOTE: ListPreference's summary should be set to its value by the activity code. -->
|
||||
<ListPreference
|
||||
android:key="sync_frequency"
|
||||
android:title="@string/pref_title_sync_frequency"
|
||||
android:entries="@array/pref_sync_frequency_titles"
|
||||
android:entryValues="@array/pref_sync_frequency_values"
|
||||
android:defaultValue="180"
|
||||
android:negativeButtonText="@null"
|
||||
android:positiveButtonText="@null" />
|
||||
|
||||
<!-- This preference simply launches an intent when selected. Use this UI sparingly, per
|
||||
design guidelines. -->
|
||||
<Preference android:title="@string/pref_title_system_sync_settings">
|
||||
<intent android:action="android.settings.SYNC_SETTINGS" />
|
||||
</Preference>
|
||||
|
||||
</PreferenceScreen>
|
||||
@@ -0,0 +1,33 @@
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="example_checkbox"
|
||||
android:title="@string/pref_title_social_recommendations"
|
||||
android:summary="@string/pref_description_social_recommendations"
|
||||
android:defaultValue="true" />
|
||||
|
||||
<!-- NOTE: EditTextPreference accepts EditText attributes. -->
|
||||
<!-- NOTE: EditTextPreference's summary should be set to its value by the activity code. -->
|
||||
<EditTextPreference
|
||||
android:key="example_text"
|
||||
android:title="@string/pref_title_display_name"
|
||||
android:defaultValue="@string/pref_default_display_name"
|
||||
android:selectAllOnFocus="true"
|
||||
android:inputType="textCapWords"
|
||||
android:capitalize="words"
|
||||
android:singleLine="true"
|
||||
android:maxLines="1" />
|
||||
|
||||
<!-- NOTE: Hide buttons to simplify the UI. Users can touch outside the dialog to
|
||||
dismiss it. -->
|
||||
<!-- NOTE: ListPreference's summary should be set to its value by the activity code. -->
|
||||
<ListPreference
|
||||
android:key="example_list"
|
||||
android:title="@string/pref_title_add_friends_to_messages"
|
||||
android:defaultValue="-1"
|
||||
android:entries="@array/pref_example_list_titles"
|
||||
android:entryValues="@array/pref_example_list_values"
|
||||
android:negativeButtonText="@null"
|
||||
android:positiveButtonText="@null" />
|
||||
|
||||
</PreferenceScreen>
|
||||
@@ -0,0 +1,15 @@
|
||||
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- These settings headers are only used on tablets. -->
|
||||
|
||||
<header android:fragment="nodomain.freeyourgadget.gadgetbridge.SettingsActivity$GeneralPreferenceFragment"
|
||||
android:title="@string/pref_header_general" />
|
||||
|
||||
<header
|
||||
android:fragment="nodomain.freeyourgadget.gadgetbridge.SettingsActivity$NotificationPreferenceFragment"
|
||||
android:title="@string/pref_header_notifications" />
|
||||
|
||||
<header android:fragment="nodomain.freeyourgadget.gadgetbridge.SettingsActivity$DataSyncPreferenceFragment"
|
||||
android:title="@string/pref_header_data_sync" />
|
||||
|
||||
</preference-headers>
|
||||
@@ -0,0 +1,27 @@
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- A 'parent' preference, which enables/disables child preferences (below)
|
||||
when checked/unchecked. -->
|
||||
<CheckBoxPreference
|
||||
android:key="notifications_new_message"
|
||||
android:title="@string/pref_title_new_message_notifications"
|
||||
android:defaultValue="true" />
|
||||
|
||||
<!-- Allows the user to choose a ringtone in the 'notification' category. -->
|
||||
<!-- NOTE: This preference will be enabled only when the checkbox above is checked. -->
|
||||
<!-- NOTE: RingtonePreference's summary should be set to its value by the activity code. -->
|
||||
<RingtonePreference
|
||||
android:dependency="notifications_new_message"
|
||||
android:key="notifications_new_message_ringtone"
|
||||
android:title="@string/pref_title_ringtone"
|
||||
android:ringtoneType="notification"
|
||||
android:defaultValue="content://settings/system/notification_sound" />
|
||||
|
||||
<!-- NOTE: This preference will be enabled only when the checkbox above is checked. -->
|
||||
<CheckBoxPreference
|
||||
android:dependency="notifications_new_message"
|
||||
android:key="notifications_new_message_vibrate"
|
||||
android:title="@string/pref_title_vibrate"
|
||||
android:defaultValue="true" />
|
||||
|
||||
</PreferenceScreen>
|
||||
Reference in New Issue
Block a user