Commit 95ed0ddb authored by Lukas Fülling's avatar Lukas Fülling

add companion config activity, write a lot of javadoc, smaller fixes

parent c7b8db2c
......@@ -7,8 +7,8 @@ android {
applicationId "io.lerk.vaporface"
minSdkVersion 24
targetSdkVersion 27
versionCode 7
versionName "1.3.1"
versionCode 8
versionName "1.4"
multiDexEnabled true
}
buildTypes {
......@@ -27,6 +27,7 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.google.android.support:wearable:2.1.0'
implementation 'com.google.android.gms:play-services-wearable:11.4.2'
implementation 'com.android.support:design:27.0.0'
implementation 'com.android.support:percent:27.0.0'
implementation 'com.android.support:support-v4:27.0.0'
implementation 'com.android.support:recyclerview-v7:27.0.0'
......
......@@ -18,7 +18,11 @@
android:theme="@android:style/Theme.DeviceDefault">
<meta-data
android:name="com.google.android.wearable.standalone"
android:value="true" />
android:value="true"/>
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version"/>
<uses-library
android:name="com.google.android.wearable"
android:required="false"/>
......@@ -41,6 +45,10 @@
android:name="com.google.android.wearable.watchface.wearableConfigurationAction"
android:value="io.lerk.android.wearable.watchface.CONFIG_COMPLICATION"/>
<meta-data
android:name="com.google.android.wearable.watchface.companionConfigurationAction"
android:value="io.lerk.android.wearable.watchface.CONFIG_WATCHFACE"/>
<intent-filter>
<action android:name="android.service.wallpaper.WallpaperService" />
......@@ -50,6 +58,16 @@
<activity android:name="android.support.wearable.complications.ComplicationHelperActivity"/>
<activity
android:name=".companion.ConfigActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="io.lerk.android.wearable.watchface.CONFIG_WATCHFACE" />
<category android:name="com.google.android.wearable.watchface.category.COMPANION_CONFIGURATION" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".VaporFaceConfigActivity"
android:label="@string/app_name">
......@@ -59,10 +77,6 @@
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
</application>
</manifest>
\ No newline at end of file
This diff is collapsed.
......@@ -23,7 +23,6 @@ import java.util.concurrent.Executors;
*
* @author Lukas Fülling (lukas@k40s.net)
*/
public class VaporFaceConfigActivity extends Activity implements View.OnClickListener {
private static final String TAG = "ConfigActivity";
......@@ -110,7 +109,7 @@ public class VaporFaceConfigActivity extends Activity implements View.OnClickLis
private void togglePrevBg() {
currentBG--;
if (currentBG < 0) {
if (currentBG <= 0) {
currentBG = 0;
bgChangeLeft.setVisibility(View.GONE);
bgChangeRight.setVisibility(View.VISIBLE);
......@@ -124,7 +123,7 @@ public class VaporFaceConfigActivity extends Activity implements View.OnClickLis
private void toggleNextBg() {
currentBG++;
if (currentBG > 7) {
if (currentBG >= 7) {
currentBG = 7;
bgChangeRight.setVisibility(View.GONE);
bgChangeLeft.setVisibility(View.VISIBLE);
......
package io.lerk.vaporface;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.util.Log;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.wearable.DataApi;
import com.google.android.gms.wearable.DataItem;
import com.google.android.gms.wearable.DataMap;
import com.google.android.gms.wearable.DataMapItem;
import com.google.android.gms.wearable.PutDataMapRequest;
import com.google.android.gms.wearable.Wearable;
public final class VaporUtils {
private static final String TAG = VaporUtils.class.getCanonicalName();
public static final String PATH_WITH_FEATURE = "/watch_face_config/VaporFace";
public static final String KEY_BACKGROUND_IMAGE = "background";
public static final String KEY_ENABLE_ANIMATION = "animations_enabled";
public static final String PREFERENCES_NAME = "vaporface";
/**
* Callback interface to perform an action with the current config {@link DataMap} for
* {@link VaporFace}.
*/
public interface FetchConfigDataMapCallback {
/**
* Callback invoked with the current config {@link DataMap} for
* {@link VaporFace}.
*/
void onConfigDataMapFetched(DataMap config);
}
/**
* Asynchronously fetches the current config {@link DataMap} for {@link VaporFace}
* and passes it to the given callback.
* <p>
* If the current config {@link DataItem} doesn't exist, it isn't created and the callback
* receives an empty DataMap.
*/
public static void fetchConfigDataMap(final GoogleApiClient client,
final FetchConfigDataMapCallback callback) {
Wearable.NodeApi.getLocalNode(client).setResultCallback(
getLocalNodeResult -> {
String localNode = getLocalNodeResult.getNode().getId();
Uri uri = new Uri.Builder()
.scheme("wear")
.path(PATH_WITH_FEATURE)
.authority(localNode)
.build();
Wearable.DataApi.getDataItem(client, uri)
.setResultCallback(new DataItemResultCallback(callback));
}
);
}
/**
* Overwrites (or sets, if not present) the keys in the current config {@link DataItem} with
* the ones appearing in the given {@link DataMap}. If the config DataItem doesn't exist,
* it's created.
* <p>
* It is allowed that only some of the keys used in the config DataItem appear in
* {@code configKeysToOverwrite}. The rest of the keys remains unmodified in this case.
*/
public static void overwriteKeysInConfigDataMap(final GoogleApiClient googleApiClient,
final DataMap configKeysToOverwrite) {
VaporUtils.fetchConfigDataMap(googleApiClient,
currentConfig -> {
DataMap overwrittenConfig = new DataMap();
overwrittenConfig.putAll(currentConfig);
overwrittenConfig.putAll(configKeysToOverwrite);
VaporUtils.putConfigDataItem(googleApiClient, overwrittenConfig);
}
);
}
/**
* Overwrites the current config {@link DataItem}'s {@link DataMap} with {@code newConfig}.
* If the config DataItem doesn't exist, it's created.
*/
public static void putConfigDataItem(GoogleApiClient googleApiClient, DataMap newConfig) {
PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(PATH_WITH_FEATURE);
putDataMapRequest.setUrgent();
DataMap configToPut = putDataMapRequest.getDataMap();
configToPut.putAll(newConfig);
Wearable.DataApi.putDataItem(googleApiClient, putDataMapRequest.asPutDataRequest())
.setResultCallback(dataItemResult -> {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "putDataItem result status: " + dataItemResult.getStatus());
}
});
}
private static class DataItemResultCallback implements ResultCallback<DataApi.DataItemResult> {
private final FetchConfigDataMapCallback mCallback;
public DataItemResultCallback(FetchConfigDataMapCallback callback) {
mCallback = callback;
}
@Override
public void onResult(@NonNull DataApi.DataItemResult dataItemResult) {
if (dataItemResult.getStatus().isSuccess()) {
if (dataItemResult.getDataItem() != null) {
DataItem configDataItem = dataItemResult.getDataItem();
DataMapItem dataMapItem = DataMapItem.fromDataItem(configDataItem);
DataMap config = dataMapItem.getDataMap();
mCallback.onConfigDataMapFetched(config);
} else {
mCallback.onConfigDataMapFetched(new DataMap());
}
}
}
}
private VaporUtils() {
}
}
package io.lerk.vaporface.companion;
import android.app.Activity;
import android.app.AlertDialog;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.wearable.companion.WatchFaceCompanion;
import android.util.Log;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.Switch;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.wearable.DataApi;
import com.google.android.gms.wearable.DataItem;
import com.google.android.gms.wearable.DataMap;
import com.google.android.gms.wearable.DataMapItem;
import com.google.android.gms.wearable.Wearable;
import io.lerk.vaporface.R;
import io.lerk.vaporface.VaporUtils;
/**
* The phone-side config activity for {@code DigitalWatchFaceService}. Like the watch-side config
* activity ({@code DigitalWatchFaceWearableConfigActivity}), allows for setting the background
* color. Additionally, enables setting the color for hour, minute and second digits.
*/
public class ConfigActivity extends Activity
implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener,
ResultCallback<DataApi.DataItemResult> {
private static final String TAG = ConfigActivity.class.getCanonicalName();
private GoogleApiClient googleApiClient;
private String peerId;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_config_companion);
peerId = getIntent().getStringExtra(WatchFaceCompanion.EXTRA_PEER_ID);
googleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(Wearable.API)
.build();
}
@Override
protected void onStart() {
super.onStart();
googleApiClient.connect();
}
@Override
protected void onStop() {
if (googleApiClient != null && googleApiClient.isConnected()) {
googleApiClient.disconnect();
}
super.onStop();
}
@Override // GoogleApiClient.ConnectionCallbacks
public void onConnected(Bundle connectionHint) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "connected: " + connectionHint);
}
if (peerId != null) {
Uri.Builder builder = new Uri.Builder();
Uri uri = builder.scheme("wear").path(VaporUtils.PATH_WITH_FEATURE).authority(peerId).build();
Wearable.DataApi.getDataItem(googleApiClient, uri).setResultCallback(this);
} else {
displayNoConnectedDeviceDialog();
}
}
@Override // ResultCallback<DataApi.DataItemResult>
public void onResult(@NonNull DataApi.DataItemResult dataItemResult) {
if (dataItemResult.getStatus().isSuccess() && dataItemResult.getDataItem() != null) {
DataItem configDataItem = dataItemResult.getDataItem();
DataMapItem dataMapItem = DataMapItem.fromDataItem(configDataItem);
DataMap config = dataMapItem.getDataMap();
initViews(config);
} else {
// If DataItem with the current config can't be retrieved, select the default items on
// each picker.
initViews(null);
}
}
@Override // GoogleApiClient.ConnectionCallbacks
public void onConnectionSuspended(int cause) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "connection suspended: " + cause);
}
}
@Override // GoogleApiClient.OnConnectionFailedListener
public void onConnectionFailed(@NonNull ConnectionResult result) {
Snackbar.make(findViewById(R.id.companion_config_activity),
R.string.companion_connection_failed,
Snackbar.LENGTH_LONG).show();
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "connection failed: " + result);
}
}
private void displayNoConnectedDeviceDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
String messageText = getResources().getString(R.string.title_no_device_connected);
String okText = getResources().getString(R.string.okay);
builder.setMessage(messageText)
.setCancelable(false)
.setPositiveButton(okText, (dialog, id) -> {});
AlertDialog alert = builder.create();
alert.show();
}
private void initViews(DataMap config) {
ImageView backgroundPreview = findViewById(R.id.companion_config_preview);
SeekBar backgroundSelector = findViewById(R.id.companion_config_background_selector);
Switch animationToggle = findViewById(R.id.companion_config_animations_toggle);
backgroundSelector.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
setBackgroundPreview(i, backgroundPreview);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
Log.d(TAG, "SeekBar: tracking started");
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
Log.d(TAG, "SeekBar: tracking stopped.");
sendBackgroundUpdate(String.valueOf(seekBar.getProgress()));
Log.d(TAG, "SeekBar: Message sent.");
}
});
animationToggle.setOnCheckedChangeListener((compoundButton, b) -> sendAnimationUpdate(b));
if(config != null) {
animationToggle.setChecked(config.getBoolean(VaporUtils.KEY_ENABLE_ANIMATION));
setBackgroundPreview(Integer.parseInt(config.getString(VaporUtils.KEY_BACKGROUND_IMAGE)), backgroundPreview);
}
}
private void setBackgroundPreview(int background, ImageView view) {
switch (background) {
case 1:
view.setImageDrawable(getDrawable(R.drawable.bg_04_01));
break;
case 2:
view.setImageDrawable(getDrawable(R.drawable.bg_08_01));
break;
case 3:
view.setImageDrawable(getDrawable(R.drawable.bg_10_01));
break;
case 4:
view.setImageDrawable(getDrawable(R.drawable.bg_12_01));
break;
case 5:
view.setImageDrawable(getDrawable(R.drawable.bg_15_01));
break;
case 6:
view.setImageDrawable(getDrawable(R.drawable.bg_16_01));
break;
case 7:
view.setImageDrawable(getDrawable(R.drawable.bg_20_01));
break;
case 0:
default:
view.setImageDrawable(getDrawable(R.drawable.vaporwave_grid));
break;
}
}
private void sendBackgroundUpdate(String value) {
if (peerId != null) {
DataMap config = new DataMap();
config.putString(VaporUtils.KEY_BACKGROUND_IMAGE, value);
byte[] rawData = config.toByteArray();
Wearable.MessageApi.sendMessage(googleApiClient, peerId, VaporUtils.PATH_WITH_FEATURE, rawData);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "config message: " + VaporUtils.KEY_BACKGROUND_IMAGE + " -> " + value);
}
}
}
private void sendAnimationUpdate(Boolean value) {
if (peerId != null) {
DataMap config = new DataMap();
config.putBoolean(VaporUtils.KEY_ENABLE_ANIMATION, value);
byte[] rawData = config.toByteArray();
Wearable.MessageApi.sendMessage(googleApiClient, peerId, VaporUtils.PATH_WITH_FEATURE, rawData);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "config message: " + VaporUtils.KEY_ENABLE_ANIMATION + " -> " + value);
}
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2014 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<RelativeLayout 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"
android:layout_width="match_parent"
android:id="@+id/companion_config_activity"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/companion_config_text"
android:text="@string/companion_config_text"
android:layout_width="match_parent"
android:textAlignment="center"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginTop="8dp"/>
<ImageView
android:id="@+id/companion_config_preview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/companion_config_text"
android:layout_centerHorizontal="true"
android:src="@drawable/vaporwave_grid"
android:contentDescription="@string/companion_config_preview_description" />
<SeekBar
android:id="@+id/companion_config_background_selector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/companion_config_preview"
android:layout_marginTop="16dp"
android:layout_centerHorizontal="true"
android:max="7"
android:progress="0" />
<RelativeLayout
android:id="@+id/companion_config_switch_group"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_below="@id/companion_config_background_selector"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:layout_marginTop="24dp">
<TextView
android:layout_alignParentTop="true"
android:id="@+id/companion_config_animations"
android:layout_width="wrap_content"
android:layout_alignParentStart="true"
android:layout_height="wrap_content"
android:text="@string/companion_config_animations"
tools:ignore="RelativeOverlap" />
<TextView
android:id="@+id/companion_config_animations_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_below="@id/companion_config_animations"
android:textStyle="italic"
android:textColor="@color/secondary_text_light"
android:text="@string/companion_config_animations_description"/>
<Switch
android:id="@+id/companion_config_animations_toggle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentEnd="true"/>
</RelativeLayout>
</RelativeLayout>
\ No newline at end of file
......@@ -4,4 +4,11 @@
<string name="my_digital_name">Vapor</string>
<string name="vapor_settings" translatable="false">🌴⚙</string>
<string name="animation_toggle">Animations</string>
<string name="companion_config_text">Watchface Background Settings</string>
<string name="companion_config_animations">Enable Animations</string>
<string name="companion_config_animations_description">Animations can use more battery.</string>
<string name="title_no_device_connected">Unable to reach device...</string>
<string name="okay">Okay</string>
<string name="companion_config_preview_description">Background Preview</string>
<string name="companion_connection_failed">Connection Failed</string>
</resources>
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