Update prebuilts for mnc-docs
am: 7e9f0673a1
Change-Id: I8d28c2cda1f5a39251f508608fec6807cb115a77
@@ -41,8 +41,6 @@ import com.google.android.gms.common.api.ResultCallback;
|
|||||||
import com.google.android.gms.wearable.DataApi;
|
import com.google.android.gms.wearable.DataApi;
|
||||||
import com.google.android.gms.wearable.DataItem;
|
import com.google.android.gms.wearable.DataItem;
|
||||||
import com.google.android.gms.wearable.DataItemBuffer;
|
import com.google.android.gms.wearable.DataItemBuffer;
|
||||||
import com.google.android.gms.wearable.Node;
|
|
||||||
import com.google.android.gms.wearable.NodeApi;
|
|
||||||
import com.google.android.gms.wearable.Wearable;
|
import com.google.android.gms.wearable.Wearable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -51,7 +49,6 @@ import com.google.android.gms.wearable.Wearable;
|
|||||||
* permissions as well.
|
* permissions as well.
|
||||||
*/
|
*/
|
||||||
public class MainActivity extends AppCompatActivity implements
|
public class MainActivity extends AppCompatActivity implements
|
||||||
NodeApi.NodeListener,
|
|
||||||
ConnectionCallbacks,
|
ConnectionCallbacks,
|
||||||
OnConnectionFailedListener,
|
OnConnectionFailedListener,
|
||||||
ActivityCompat.OnRequestPermissionsResultCallback {
|
ActivityCompat.OnRequestPermissionsResultCallback {
|
||||||
@@ -66,11 +63,11 @@ public class MainActivity extends AppCompatActivity implements
|
|||||||
private GoogleApiClient mGoogleApiClient;
|
private GoogleApiClient mGoogleApiClient;
|
||||||
private boolean mResolvingError = false;
|
private boolean mResolvingError = false;
|
||||||
|
|
||||||
private TextView mLogTextView;
|
|
||||||
ScrollView mScroller;
|
|
||||||
|
|
||||||
private View mLayout;
|
private View mLayout;
|
||||||
|
|
||||||
|
private TextView mLogTextView;
|
||||||
|
private ScrollView mScroller;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -98,10 +95,10 @@ public class MainActivity extends AppCompatActivity implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStop() {
|
protected void onStop() {
|
||||||
if (mGoogleApiClient.isConnected()) {
|
if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
|
||||||
Wearable.NodeApi.removeListener(mGoogleApiClient, this);
|
mGoogleApiClient.disconnect();
|
||||||
}
|
}
|
||||||
mGoogleApiClient.disconnect();
|
|
||||||
super.onStop();
|
super.onStop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,10 +129,6 @@ public class MainActivity extends AppCompatActivity implements
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void pushCalendarToWear() {
|
|
||||||
startService(new Intent(this, CalendarQueryService.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Requests Calendar and Contact permissions.
|
* Requests Calendar and Contact permissions.
|
||||||
* If the permission has been denied previously, a SnackBar will prompt the user to grant the
|
* If the permission has been denied previously, a SnackBar will prompt the user to grant the
|
||||||
@@ -190,7 +183,9 @@ public class MainActivity extends AppCompatActivity implements
|
|||||||
// END_INCLUDE(calendar_and_contact_permissions_request)
|
// END_INCLUDE(calendar_and_contact_permissions_request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void pushCalendarToWear() {
|
||||||
|
startService(new Intent(this, CalendarQueryService.class));
|
||||||
|
}
|
||||||
|
|
||||||
public void onDeleteEventsClicked(View view) {
|
public void onDeleteEventsClicked(View view) {
|
||||||
if (mGoogleApiClient.isConnected()) {
|
if (mGoogleApiClient.isConnected()) {
|
||||||
@@ -245,28 +240,19 @@ public class MainActivity extends AppCompatActivity implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPeerConnected(Node peer) {
|
|
||||||
appendLog("Device connected");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPeerDisconnected(Node peer) {
|
|
||||||
appendLog("Device disconnected");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConnected(Bundle connectionHint) {
|
public void onConnected(Bundle connectionHint) {
|
||||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||||
Log.d(TAG, "Connected to Google Api Service.");
|
Log.d(TAG, "Connected to Google Api Service.");
|
||||||
}
|
}
|
||||||
mResolvingError = false;
|
mResolvingError = false;
|
||||||
Wearable.NodeApi.addListener(mGoogleApiClient, this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConnectionSuspended(int cause) {
|
public void onConnectionSuspended(int cause) {
|
||||||
// Ignore
|
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||||
|
Log.d(TAG, "onConnectionSuspended(): Cause id: " + cause);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -274,9 +260,7 @@ public class MainActivity extends AppCompatActivity implements
|
|||||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||||
Log.d(TAG, "Disconnected from Google Api Service");
|
Log.d(TAG, "Disconnected from Google Api Service");
|
||||||
}
|
}
|
||||||
if (null != Wearable.NodeApi) {
|
|
||||||
Wearable.NodeApi.removeListener(mGoogleApiClient, this);
|
|
||||||
}
|
|
||||||
if (mResolvingError) {
|
if (mResolvingError) {
|
||||||
// Already attempting to resolve an error.
|
// Already attempting to resolve an error.
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
package="com.example.android.wearable.agendadata" >
|
package="com.example.android.wearable.agendadata" >
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="20"
|
<uses-sdk android:minSdkVersion="20"
|
||||||
android:targetSdkVersion="22" />
|
android:targetSdkVersion="23" />
|
||||||
|
|
||||||
<uses-feature android:name="android.hardware.type.watch" />
|
<uses-feature android:name="android.hardware.type.watch" />
|
||||||
|
|
||||||
@@ -34,7 +34,8 @@
|
|||||||
<service
|
<service
|
||||||
android:name="com.example.android.wearable.agendadata.HomeListenerService" >
|
android:name="com.example.android.wearable.agendadata.HomeListenerService" >
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
|
<action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
|
||||||
|
<data android:scheme="wear" android:host="*" android:pathPrefix="/"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
|||||||
@@ -16,9 +16,6 @@
|
|||||||
|
|
||||||
package com.example.android.wearable.agendadata;
|
package com.example.android.wearable.agendadata;
|
||||||
|
|
||||||
import static com.example.android.wearable.agendadata.Constants.TAG;
|
|
||||||
import static com.example.android.wearable.agendadata.Constants.EXTRA_SILENT;
|
|
||||||
|
|
||||||
import android.app.IntentService;
|
import android.app.IntentService;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@@ -26,6 +23,9 @@ import android.os.Bundle;
|
|||||||
import android.support.wearable.activity.ConfirmationActivity;
|
import android.support.wearable.activity.ConfirmationActivity;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import static com.example.android.wearable.agendadata.Constants.TAG;
|
||||||
|
import static com.example.android.wearable.agendadata.Constants.EXTRA_SILENT;
|
||||||
|
|
||||||
import com.google.android.gms.common.ConnectionResult;
|
import com.google.android.gms.common.ConnectionResult;
|
||||||
import com.google.android.gms.common.api.GoogleApiClient;
|
import com.google.android.gms.common.api.GoogleApiClient;
|
||||||
import com.google.android.gms.wearable.DataApi;
|
import com.google.android.gms.wearable.DataApi;
|
||||||
@@ -105,14 +105,23 @@ public class DeleteService extends IntentService implements GoogleApiClient.Conn
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConnected(Bundle connectionHint) {
|
public void onConnected(Bundle connectionHint) {
|
||||||
|
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||||
|
Log.d(TAG, "onConnected: " + connectionHint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConnectionSuspended(int cause) {
|
public void onConnectionSuspended(int cause) {
|
||||||
|
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||||
|
Log.d(TAG, "onConnectionSuspended: " + cause);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConnectionFailed(ConnectionResult result) {
|
public void onConnectionFailed(ConnectionResult result) {
|
||||||
|
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||||
|
Log.d(TAG, "onConnectionFailed: " + result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,16 +16,6 @@
|
|||||||
|
|
||||||
package com.example.android.wearable.agendadata;
|
package com.example.android.wearable.agendadata;
|
||||||
|
|
||||||
import static com.example.android.wearable.agendadata.Constants.TAG;
|
|
||||||
import static com.example.android.wearable.agendadata.Constants.EXTRA_SILENT;
|
|
||||||
|
|
||||||
import static com.example.android.wearable.agendadata.Constants.ALL_DAY;
|
|
||||||
import static com.example.android.wearable.agendadata.Constants.BEGIN;
|
|
||||||
import static com.example.android.wearable.agendadata.Constants.DESCRIPTION;
|
|
||||||
import static com.example.android.wearable.agendadata.Constants.END;
|
|
||||||
import static com.example.android.wearable.agendadata.Constants.PROFILE_PIC;
|
|
||||||
import static com.example.android.wearable.agendadata.Constants.TITLE;
|
|
||||||
|
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
@@ -37,6 +27,15 @@ import android.text.TextUtils;
|
|||||||
import android.text.format.DateFormat;
|
import android.text.format.DateFormat;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import static com.example.android.wearable.agendadata.Constants.ALL_DAY;
|
||||||
|
import static com.example.android.wearable.agendadata.Constants.BEGIN;
|
||||||
|
import static com.example.android.wearable.agendadata.Constants.DESCRIPTION;
|
||||||
|
import static com.example.android.wearable.agendadata.Constants.EXTRA_SILENT;
|
||||||
|
import static com.example.android.wearable.agendadata.Constants.END;
|
||||||
|
import static com.example.android.wearable.agendadata.Constants.PROFILE_PIC;
|
||||||
|
import static com.example.android.wearable.agendadata.Constants.TAG;
|
||||||
|
import static com.example.android.wearable.agendadata.Constants.TITLE;
|
||||||
|
|
||||||
import com.google.android.gms.common.api.GoogleApiClient;
|
import com.google.android.gms.common.api.GoogleApiClient;
|
||||||
import com.google.android.gms.wearable.Asset;
|
import com.google.android.gms.wearable.Asset;
|
||||||
import com.google.android.gms.wearable.DataApi;
|
import com.google.android.gms.wearable.DataApi;
|
||||||
@@ -45,7 +44,6 @@ import com.google.android.gms.wearable.DataEventBuffer;
|
|||||||
import com.google.android.gms.wearable.DataItem;
|
import com.google.android.gms.wearable.DataItem;
|
||||||
import com.google.android.gms.wearable.DataMap;
|
import com.google.android.gms.wearable.DataMap;
|
||||||
import com.google.android.gms.wearable.DataMapItem;
|
import com.google.android.gms.wearable.DataMapItem;
|
||||||
import com.google.android.gms.wearable.MessageEvent;
|
|
||||||
import com.google.android.gms.wearable.Wearable;
|
import com.google.android.gms.wearable.Wearable;
|
||||||
import com.google.android.gms.wearable.WearableListenerService;
|
import com.google.android.gms.wearable.WearableListenerService;
|
||||||
|
|
||||||
@@ -63,12 +61,22 @@ public class HomeListenerService extends WearableListenerService {
|
|||||||
private static int sNotificationId = 1;
|
private static int sNotificationId = 1;
|
||||||
private GoogleApiClient mGoogleApiClient;
|
private GoogleApiClient mGoogleApiClient;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
mGoogleApiClient = new GoogleApiClient.Builder(this.getApplicationContext())
|
||||||
|
.addApi(Wearable.API)
|
||||||
|
.build();
|
||||||
|
mGoogleApiClient.connect();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDataChanged(DataEventBuffer dataEvents) {
|
public void onDataChanged(DataEventBuffer dataEvents) {
|
||||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||||
Log.d(TAG, "onDataChanged: " + dataEvents + " for " + getPackageName());
|
Log.d(TAG, "onDataChanged: " + dataEvents + " for " + getPackageName());
|
||||||
}
|
}
|
||||||
for (DataEvent event : dataEvents) {
|
for (DataEvent event : dataEvents) {
|
||||||
|
|
||||||
if (event.getType() == DataEvent.TYPE_DELETED) {
|
if (event.getType() == DataEvent.TYPE_DELETED) {
|
||||||
deleteDataItem(event.getDataItem());
|
deleteDataItem(event.getDataItem());
|
||||||
} else if (event.getType() == DataEvent.TYPE_CHANGED) {
|
} else if (event.getType() == DataEvent.TYPE_CHANGED) {
|
||||||
@@ -77,13 +85,17 @@ public class HomeListenerService extends WearableListenerService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public void onCreate() {
|
* Deletes the calendar card associated with the data item.
|
||||||
super.onCreate();
|
*/
|
||||||
mGoogleApiClient = new GoogleApiClient.Builder(this.getApplicationContext())
|
private void deleteDataItem(DataItem dataItem) {
|
||||||
.addApi(Wearable.API)
|
if (Log.isLoggable(TAG, Log.VERBOSE)) {
|
||||||
.build();
|
Log.v(TAG, "onDataItemDeleted:DataItem=" + dataItem.getUri());
|
||||||
mGoogleApiClient.connect();
|
}
|
||||||
|
Integer notificationId = sNotificationIdByDataItemUri.remove(dataItem.getUri());
|
||||||
|
if (notificationId != null) {
|
||||||
|
((NotificationManager) getSystemService(NOTIFICATION_SERVICE)).cancel(notificationId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -154,26 +166,4 @@ public class HomeListenerService extends WearableListenerService {
|
|||||||
|
|
||||||
sNotificationIdByDataItemUri.put(dataItem.getUri(), sNotificationId++);
|
sNotificationIdByDataItemUri.put(dataItem.getUri(), sNotificationId++);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the calendar card associated with the data item.
|
|
||||||
*/
|
|
||||||
private void deleteDataItem(DataItem dataItem) {
|
|
||||||
if (Log.isLoggable(TAG, Log.VERBOSE)) {
|
|
||||||
Log.v(TAG, "onDataItemDeleted:DataItem=" + dataItem.getUri());
|
|
||||||
}
|
|
||||||
Integer notificationId = sNotificationIdByDataItemUri.remove(dataItem.getUri());
|
|
||||||
if (notificationId != null) {
|
|
||||||
((NotificationManager) getSystemService(NOTIFICATION_SERVICE)).cancel(notificationId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onMessageReceived(MessageEvent messageEvent) {
|
|
||||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
|
||||||
Log.d(TAG, "onMessageReceived: " + messageEvent.getPath()
|
|
||||||
+ " " + messageEvent.getData() + " for " + getPackageName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,49 +36,57 @@ public class CameraHelper {
|
|||||||
public static final int MEDIA_TYPE_VIDEO = 2;
|
public static final int MEDIA_TYPE_VIDEO = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Iterate over supported camera preview sizes to see which one best fits the
|
* Iterate over supported camera video sizes to see which one best fits the
|
||||||
* dimensions of the given view while maintaining the aspect ratio. If none can,
|
* dimensions of the given view while maintaining the aspect ratio. If none can,
|
||||||
* be lenient with the aspect ratio.
|
* be lenient with the aspect ratio.
|
||||||
*
|
*
|
||||||
* @param sizes Supported camera preview sizes.
|
* @param supportedVideoSizes Supported camera video sizes.
|
||||||
* @param w The width of the view.
|
* @param previewSizes Supported camera preview sizes.
|
||||||
* @param h The height of the view.
|
* @param w The width of the view.
|
||||||
* @return Best match camera preview size to fit in the view.
|
* @param h The height of the view.
|
||||||
|
* @return Best match camera video size to fit in the view.
|
||||||
*/
|
*/
|
||||||
public static Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
|
public static Camera.Size getOptimalVideoSize(List<Camera.Size> supportedVideoSizes,
|
||||||
|
List<Camera.Size> previewSizes, int w, int h) {
|
||||||
// Use a very small tolerance because we want an exact match.
|
// Use a very small tolerance because we want an exact match.
|
||||||
final double ASPECT_TOLERANCE = 0.1;
|
final double ASPECT_TOLERANCE = 0.1;
|
||||||
double targetRatio = (double) w / h;
|
double targetRatio = (double) w / h;
|
||||||
if (sizes == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
|
// Supported video sizes list might be null, it means that we are allowed to use the preview
|
||||||
|
// sizes
|
||||||
|
List<Camera.Size> videoSizes;
|
||||||
|
if (supportedVideoSizes != null) {
|
||||||
|
videoSizes = supportedVideoSizes;
|
||||||
|
} else {
|
||||||
|
videoSizes = previewSizes;
|
||||||
|
}
|
||||||
Camera.Size optimalSize = null;
|
Camera.Size optimalSize = null;
|
||||||
|
|
||||||
// Start with max value and refine as we iterate over available preview sizes. This is the
|
// Start with max value and refine as we iterate over available video sizes. This is the
|
||||||
// minimum difference between view and camera height.
|
// minimum difference between view and camera height.
|
||||||
double minDiff = Double.MAX_VALUE;
|
double minDiff = Double.MAX_VALUE;
|
||||||
|
|
||||||
// Target view height
|
// Target view height
|
||||||
int targetHeight = h;
|
int targetHeight = h;
|
||||||
|
|
||||||
// Try to find a preview size that matches aspect ratio and the target view size.
|
// Try to find a video size that matches aspect ratio and the target view size.
|
||||||
// Iterate over all available sizes and pick the largest size that can fit in the view and
|
// Iterate over all available sizes and pick the largest size that can fit in the view and
|
||||||
// still maintain the aspect ratio.
|
// still maintain the aspect ratio.
|
||||||
for (Camera.Size size : sizes) {
|
for (Camera.Size size : videoSizes) {
|
||||||
double ratio = (double) size.width / size.height;
|
double ratio = (double) size.width / size.height;
|
||||||
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
|
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
|
||||||
continue;
|
continue;
|
||||||
if (Math.abs(size.height - targetHeight) < minDiff) {
|
if (Math.abs(size.height - targetHeight) < minDiff && previewSizes.contains(size)) {
|
||||||
optimalSize = size;
|
optimalSize = size;
|
||||||
minDiff = Math.abs(size.height - targetHeight);
|
minDiff = Math.abs(size.height - targetHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cannot find preview size that matches the aspect ratio, ignore the requirement
|
// Cannot find video size that matches the aspect ratio, ignore the requirement
|
||||||
if (optimalSize == null) {
|
if (optimalSize == null) {
|
||||||
minDiff = Double.MAX_VALUE;
|
minDiff = Double.MAX_VALUE;
|
||||||
for (Camera.Size size : sizes) {
|
for (Camera.Size size : videoSizes) {
|
||||||
if (Math.abs(size.height - targetHeight) < minDiff) {
|
if (Math.abs(size.height - targetHeight) < minDiff && previewSizes.contains(size)) {
|
||||||
optimalSize = size;
|
optimalSize = size;
|
||||||
minDiff = Math.abs(size.height - targetHeight);
|
minDiff = Math.abs(size.height - targetHeight);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,49 +36,57 @@ public class CameraHelper {
|
|||||||
public static final int MEDIA_TYPE_VIDEO = 2;
|
public static final int MEDIA_TYPE_VIDEO = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Iterate over supported camera preview sizes to see which one best fits the
|
* Iterate over supported camera video sizes to see which one best fits the
|
||||||
* dimensions of the given view while maintaining the aspect ratio. If none can,
|
* dimensions of the given view while maintaining the aspect ratio. If none can,
|
||||||
* be lenient with the aspect ratio.
|
* be lenient with the aspect ratio.
|
||||||
*
|
*
|
||||||
* @param sizes Supported camera preview sizes.
|
* @param supportedVideoSizes Supported camera video sizes.
|
||||||
* @param w The width of the view.
|
* @param previewSizes Supported camera preview sizes.
|
||||||
* @param h The height of the view.
|
* @param w The width of the view.
|
||||||
* @return Best match camera preview size to fit in the view.
|
* @param h The height of the view.
|
||||||
|
* @return Best match camera video size to fit in the view.
|
||||||
*/
|
*/
|
||||||
public static Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
|
public static Camera.Size getOptimalVideoSize(List<Camera.Size> supportedVideoSizes,
|
||||||
|
List<Camera.Size> previewSizes, int w, int h) {
|
||||||
// Use a very small tolerance because we want an exact match.
|
// Use a very small tolerance because we want an exact match.
|
||||||
final double ASPECT_TOLERANCE = 0.1;
|
final double ASPECT_TOLERANCE = 0.1;
|
||||||
double targetRatio = (double) w / h;
|
double targetRatio = (double) w / h;
|
||||||
if (sizes == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
|
// Supported video sizes list might be null, it means that we are allowed to use the preview
|
||||||
|
// sizes
|
||||||
|
List<Camera.Size> videoSizes;
|
||||||
|
if (supportedVideoSizes != null) {
|
||||||
|
videoSizes = supportedVideoSizes;
|
||||||
|
} else {
|
||||||
|
videoSizes = previewSizes;
|
||||||
|
}
|
||||||
Camera.Size optimalSize = null;
|
Camera.Size optimalSize = null;
|
||||||
|
|
||||||
// Start with max value and refine as we iterate over available preview sizes. This is the
|
// Start with max value and refine as we iterate over available video sizes. This is the
|
||||||
// minimum difference between view and camera height.
|
// minimum difference between view and camera height.
|
||||||
double minDiff = Double.MAX_VALUE;
|
double minDiff = Double.MAX_VALUE;
|
||||||
|
|
||||||
// Target view height
|
// Target view height
|
||||||
int targetHeight = h;
|
int targetHeight = h;
|
||||||
|
|
||||||
// Try to find a preview size that matches aspect ratio and the target view size.
|
// Try to find a video size that matches aspect ratio and the target view size.
|
||||||
// Iterate over all available sizes and pick the largest size that can fit in the view and
|
// Iterate over all available sizes and pick the largest size that can fit in the view and
|
||||||
// still maintain the aspect ratio.
|
// still maintain the aspect ratio.
|
||||||
for (Camera.Size size : sizes) {
|
for (Camera.Size size : videoSizes) {
|
||||||
double ratio = (double) size.width / size.height;
|
double ratio = (double) size.width / size.height;
|
||||||
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
|
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
|
||||||
continue;
|
continue;
|
||||||
if (Math.abs(size.height - targetHeight) < minDiff) {
|
if (Math.abs(size.height - targetHeight) < minDiff && previewSizes.contains(size)) {
|
||||||
optimalSize = size;
|
optimalSize = size;
|
||||||
minDiff = Math.abs(size.height - targetHeight);
|
minDiff = Math.abs(size.height - targetHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cannot find preview size that matches the aspect ratio, ignore the requirement
|
// Cannot find video size that matches the aspect ratio, ignore the requirement
|
||||||
if (optimalSize == null) {
|
if (optimalSize == null) {
|
||||||
minDiff = Double.MAX_VALUE;
|
minDiff = Double.MAX_VALUE;
|
||||||
for (Camera.Size size : sizes) {
|
for (Camera.Size size : videoSizes) {
|
||||||
if (Math.abs(size.height - targetHeight) < minDiff) {
|
if (Math.abs(size.height - targetHeight) < minDiff && previewSizes.contains(size)) {
|
||||||
optimalSize = size;
|
optimalSize = size;
|
||||||
minDiff = Math.abs(size.height - targetHeight);
|
minDiff = Math.abs(size.height - targetHeight);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -473,7 +473,7 @@ public class BluetoothChatService {
|
|||||||
int bytes;
|
int bytes;
|
||||||
|
|
||||||
// Keep listening to the InputStream while connected
|
// Keep listening to the InputStream while connected
|
||||||
while (true) {
|
while (mState == STATE_CONNECTED) {
|
||||||
try {
|
try {
|
||||||
// Read from the InputStream
|
// Read from the InputStream
|
||||||
bytes = mmInStream.read(buffer);
|
bytes = mmInStream.read(buffer);
|
||||||
|
|||||||
@@ -275,6 +275,11 @@ public class Camera2BasicFragment extends Fragment
|
|||||||
*/
|
*/
|
||||||
private boolean mFlashSupported;
|
private boolean mFlashSupported;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Orientation of the camera sensor
|
||||||
|
*/
|
||||||
|
private int mSensorOrientation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link CameraCaptureSession.CaptureCallback} that handles events related to JPEG capture.
|
* A {@link CameraCaptureSession.CaptureCallback} that handles events related to JPEG capture.
|
||||||
*/
|
*/
|
||||||
@@ -515,19 +520,19 @@ public class Camera2BasicFragment extends Fragment
|
|||||||
// Find out if we need to swap dimension to get the preview size relative to sensor
|
// Find out if we need to swap dimension to get the preview size relative to sensor
|
||||||
// coordinate.
|
// coordinate.
|
||||||
int displayRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
|
int displayRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
|
||||||
int sensorOrientation =
|
//noinspection ConstantConditions
|
||||||
characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
|
mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
|
||||||
boolean swappedDimensions = false;
|
boolean swappedDimensions = false;
|
||||||
switch (displayRotation) {
|
switch (displayRotation) {
|
||||||
case Surface.ROTATION_0:
|
case Surface.ROTATION_0:
|
||||||
case Surface.ROTATION_180:
|
case Surface.ROTATION_180:
|
||||||
if (sensorOrientation == 90 || sensorOrientation == 270) {
|
if (mSensorOrientation == 90 || mSensorOrientation == 270) {
|
||||||
swappedDimensions = true;
|
swappedDimensions = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Surface.ROTATION_90:
|
case Surface.ROTATION_90:
|
||||||
case Surface.ROTATION_270:
|
case Surface.ROTATION_270:
|
||||||
if (sensorOrientation == 0 || sensorOrientation == 180) {
|
if (mSensorOrientation == 0 || mSensorOrientation == 180) {
|
||||||
swappedDimensions = true;
|
swappedDimensions = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -821,7 +826,7 @@ public class Camera2BasicFragment extends Fragment
|
|||||||
|
|
||||||
// Orientation
|
// Orientation
|
||||||
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
|
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
|
||||||
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation));
|
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation));
|
||||||
|
|
||||||
CameraCaptureSession.CaptureCallback CaptureCallback
|
CameraCaptureSession.CaptureCallback CaptureCallback
|
||||||
= new CameraCaptureSession.CaptureCallback() {
|
= new CameraCaptureSession.CaptureCallback() {
|
||||||
@@ -843,6 +848,20 @@ public class Camera2BasicFragment extends Fragment
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the JPEG orientation from the specified screen rotation.
|
||||||
|
*
|
||||||
|
* @param rotation The screen rotation.
|
||||||
|
* @return The JPEG orientation (one of 0, 90, 270, and 360)
|
||||||
|
*/
|
||||||
|
private int getOrientation(int rotation) {
|
||||||
|
// Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X)
|
||||||
|
// We have to take that into account and rotate JPEG properly.
|
||||||
|
// For devices with orientation of 90, we simply return our mapping from ORIENTATIONS.
|
||||||
|
// For devices with orientation of 270, we need to rotate the JPEG 180 degrees.
|
||||||
|
return (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unlock the focus. This method should be called when still image capture sequence is
|
* Unlock the focus. This method should be called when still image capture sequence is
|
||||||
* finished.
|
* finished.
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
<uses-permission android:name="android.permission.CAMERA"/>
|
<uses-permission android:name="android.permission.CAMERA"/>
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||||
|
|
||||||
<application android:allowBackup="true"
|
<application android:allowBackup="true"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
|||||||
@@ -55,9 +55,9 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -67,7 +67,10 @@ import java.util.concurrent.TimeUnit;
|
|||||||
public class Camera2VideoFragment extends Fragment
|
public class Camera2VideoFragment extends Fragment
|
||||||
implements View.OnClickListener, FragmentCompat.OnRequestPermissionsResultCallback {
|
implements View.OnClickListener, FragmentCompat.OnRequestPermissionsResultCallback {
|
||||||
|
|
||||||
private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
|
private static final int SENSOR_ORIENTATION_DEFAULT_DEGREES = 90;
|
||||||
|
private static final int SENSOR_ORIENTATION_INVERSE_DEGREES = 270;
|
||||||
|
private static final SparseIntArray DEFAULT_ORIENTATIONS = new SparseIntArray();
|
||||||
|
private static final SparseIntArray INVERSE_ORIENTATIONS = new SparseIntArray();
|
||||||
|
|
||||||
private static final String TAG = "Camera2VideoFragment";
|
private static final String TAG = "Camera2VideoFragment";
|
||||||
private static final int REQUEST_VIDEO_PERMISSIONS = 1;
|
private static final int REQUEST_VIDEO_PERMISSIONS = 1;
|
||||||
@@ -79,10 +82,17 @@ public class Camera2VideoFragment extends Fragment
|
|||||||
};
|
};
|
||||||
|
|
||||||
static {
|
static {
|
||||||
ORIENTATIONS.append(Surface.ROTATION_0, 90);
|
DEFAULT_ORIENTATIONS.append(Surface.ROTATION_0, 90);
|
||||||
ORIENTATIONS.append(Surface.ROTATION_90, 0);
|
DEFAULT_ORIENTATIONS.append(Surface.ROTATION_90, 0);
|
||||||
ORIENTATIONS.append(Surface.ROTATION_180, 270);
|
DEFAULT_ORIENTATIONS.append(Surface.ROTATION_180, 270);
|
||||||
ORIENTATIONS.append(Surface.ROTATION_270, 180);
|
DEFAULT_ORIENTATIONS.append(Surface.ROTATION_270, 180);
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
INVERSE_ORIENTATIONS.append(Surface.ROTATION_0, 270);
|
||||||
|
INVERSE_ORIENTATIONS.append(Surface.ROTATION_90, 180);
|
||||||
|
INVERSE_ORIENTATIONS.append(Surface.ROTATION_180, 90);
|
||||||
|
INVERSE_ORIENTATIONS.append(Surface.ROTATION_270, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -146,11 +156,6 @@ public class Camera2VideoFragment extends Fragment
|
|||||||
*/
|
*/
|
||||||
private Size mVideoSize;
|
private Size mVideoSize;
|
||||||
|
|
||||||
/**
|
|
||||||
* Camera preview.
|
|
||||||
*/
|
|
||||||
private CaptureRequest.Builder mPreviewBuilder;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MediaRecorder
|
* MediaRecorder
|
||||||
*/
|
*/
|
||||||
@@ -210,6 +215,10 @@ public class Camera2VideoFragment extends Fragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
private Integer mSensorOrientation;
|
||||||
|
private String mNextVideoAbsolutePath;
|
||||||
|
private CaptureRequest.Builder mPreviewBuilder;
|
||||||
|
private Surface mRecorderSurface;
|
||||||
|
|
||||||
public static Camera2VideoFragment newInstance() {
|
public static Camera2VideoFragment newInstance() {
|
||||||
return new Camera2VideoFragment();
|
return new Camera2VideoFragment();
|
||||||
@@ -425,6 +434,7 @@ public class Camera2VideoFragment extends Fragment
|
|||||||
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
|
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
|
||||||
StreamConfigurationMap map = characteristics
|
StreamConfigurationMap map = characteristics
|
||||||
.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
|
.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
|
||||||
|
mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
|
||||||
mVideoSize = chooseVideoSize(map.getOutputSizes(MediaRecorder.class));
|
mVideoSize = chooseVideoSize(map.getOutputSizes(MediaRecorder.class));
|
||||||
mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
|
mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
|
||||||
width, height, mVideoSize);
|
width, height, mVideoSize);
|
||||||
@@ -454,6 +464,7 @@ public class Camera2VideoFragment extends Fragment
|
|||||||
private void closeCamera() {
|
private void closeCamera() {
|
||||||
try {
|
try {
|
||||||
mCameraOpenCloseLock.acquire();
|
mCameraOpenCloseLock.acquire();
|
||||||
|
closePreviewSession();
|
||||||
if (null != mCameraDevice) {
|
if (null != mCameraDevice) {
|
||||||
mCameraDevice.close();
|
mCameraDevice.close();
|
||||||
mCameraDevice = null;
|
mCameraDevice = null;
|
||||||
@@ -477,22 +488,16 @@ public class Camera2VideoFragment extends Fragment
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
setUpMediaRecorder();
|
closePreviewSession();
|
||||||
SurfaceTexture texture = mTextureView.getSurfaceTexture();
|
SurfaceTexture texture = mTextureView.getSurfaceTexture();
|
||||||
assert texture != null;
|
assert texture != null;
|
||||||
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
|
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
|
||||||
mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
|
mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
|
||||||
List<Surface> surfaces = new ArrayList<Surface>();
|
|
||||||
|
|
||||||
Surface previewSurface = new Surface(texture);
|
Surface previewSurface = new Surface(texture);
|
||||||
surfaces.add(previewSurface);
|
|
||||||
mPreviewBuilder.addTarget(previewSurface);
|
mPreviewBuilder.addTarget(previewSurface);
|
||||||
|
|
||||||
Surface recorderSurface = mMediaRecorder.getSurface();
|
mCameraDevice.createCaptureSession(Arrays.asList(previewSurface), new CameraCaptureSession.StateCallback() {
|
||||||
surfaces.add(recorderSurface);
|
|
||||||
mPreviewBuilder.addTarget(recorderSurface);
|
|
||||||
|
|
||||||
mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConfigured(CameraCaptureSession cameraCaptureSession) {
|
public void onConfigured(CameraCaptureSession cameraCaptureSession) {
|
||||||
@@ -510,8 +515,6 @@ public class Camera2VideoFragment extends Fragment
|
|||||||
}, mBackgroundHandler);
|
}, mBackgroundHandler);
|
||||||
} catch (CameraAccessException e) {
|
} catch (CameraAccessException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -575,32 +578,96 @@ public class Camera2VideoFragment extends Fragment
|
|||||||
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
|
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
|
||||||
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
|
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
|
||||||
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
|
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
|
||||||
mMediaRecorder.setOutputFile(getVideoFile(activity).getAbsolutePath());
|
if (mNextVideoAbsolutePath == null || mNextVideoAbsolutePath.isEmpty()) {
|
||||||
|
mNextVideoAbsolutePath = getVideoFilePath(getActivity());
|
||||||
|
}
|
||||||
|
mMediaRecorder.setOutputFile(mNextVideoAbsolutePath);
|
||||||
mMediaRecorder.setVideoEncodingBitRate(10000000);
|
mMediaRecorder.setVideoEncodingBitRate(10000000);
|
||||||
mMediaRecorder.setVideoFrameRate(30);
|
mMediaRecorder.setVideoFrameRate(30);
|
||||||
mMediaRecorder.setVideoSize(mVideoSize.getWidth(), mVideoSize.getHeight());
|
mMediaRecorder.setVideoSize(mVideoSize.getWidth(), mVideoSize.getHeight());
|
||||||
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
|
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
|
||||||
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
|
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
|
||||||
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
|
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
|
||||||
int orientation = ORIENTATIONS.get(rotation);
|
switch (mSensorOrientation) {
|
||||||
mMediaRecorder.setOrientationHint(orientation);
|
case SENSOR_ORIENTATION_DEFAULT_DEGREES:
|
||||||
|
mMediaRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation));
|
||||||
|
break;
|
||||||
|
case SENSOR_ORIENTATION_INVERSE_DEGREES:
|
||||||
|
mMediaRecorder.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation));
|
||||||
|
break;
|
||||||
|
}
|
||||||
mMediaRecorder.prepare();
|
mMediaRecorder.prepare();
|
||||||
}
|
}
|
||||||
|
|
||||||
private File getVideoFile(Context context) {
|
private String getVideoFilePath(Context context) {
|
||||||
return new File(context.getExternalFilesDir(null), "video.mp4");
|
return context.getExternalFilesDir(null).getAbsolutePath() + "/"
|
||||||
|
+ System.currentTimeMillis() + ".mp4";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startRecordingVideo() {
|
private void startRecordingVideo() {
|
||||||
|
if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
// UI
|
closePreviewSession();
|
||||||
mButtonVideo.setText(R.string.stop);
|
setUpMediaRecorder();
|
||||||
mIsRecordingVideo = true;
|
SurfaceTexture texture = mTextureView.getSurfaceTexture();
|
||||||
|
assert texture != null;
|
||||||
|
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
|
||||||
|
mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
|
||||||
|
List<Surface> surfaces = new ArrayList<>();
|
||||||
|
|
||||||
// Start recording
|
// Set up Surface for the camera preview
|
||||||
mMediaRecorder.start();
|
Surface previewSurface = new Surface(texture);
|
||||||
} catch (IllegalStateException e) {
|
surfaces.add(previewSurface);
|
||||||
|
mPreviewBuilder.addTarget(previewSurface);
|
||||||
|
|
||||||
|
// Set up Surface for the MediaRecorder
|
||||||
|
mRecorderSurface = mMediaRecorder.getSurface();
|
||||||
|
surfaces.add(mRecorderSurface);
|
||||||
|
mPreviewBuilder.addTarget(mRecorderSurface);
|
||||||
|
|
||||||
|
// Start a capture session
|
||||||
|
// Once the session starts, we can update the UI and start recording
|
||||||
|
mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
|
||||||
|
mPreviewSession = cameraCaptureSession;
|
||||||
|
updatePreview();
|
||||||
|
getActivity().runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// UI
|
||||||
|
mButtonVideo.setText(R.string.stop);
|
||||||
|
mIsRecordingVideo = true;
|
||||||
|
|
||||||
|
// Start recording
|
||||||
|
mMediaRecorder.start();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
|
||||||
|
Activity activity = getActivity();
|
||||||
|
if (null != activity) {
|
||||||
|
Toast.makeText(activity, "Failed", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, mBackgroundHandler);
|
||||||
|
} catch (CameraAccessException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closePreviewSession() {
|
||||||
|
if (mPreviewSession != null) {
|
||||||
|
mPreviewSession.close();
|
||||||
|
mPreviewSession = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -611,11 +678,14 @@ public class Camera2VideoFragment extends Fragment
|
|||||||
// Stop recording
|
// Stop recording
|
||||||
mMediaRecorder.stop();
|
mMediaRecorder.stop();
|
||||||
mMediaRecorder.reset();
|
mMediaRecorder.reset();
|
||||||
|
|
||||||
Activity activity = getActivity();
|
Activity activity = getActivity();
|
||||||
if (null != activity) {
|
if (null != activity) {
|
||||||
Toast.makeText(activity, "Video saved: " + getVideoFile(activity),
|
Toast.makeText(activity, "Video saved: " + mNextVideoAbsolutePath,
|
||||||
Toast.LENGTH_SHORT).show();
|
Toast.LENGTH_SHORT).show();
|
||||||
|
Log.d(TAG, "Video saved: " + mNextVideoAbsolutePath);
|
||||||
}
|
}
|
||||||
|
mNextVideoAbsolutePath = null;
|
||||||
startPreview();
|
startPreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ import android.content.Intent;
|
|||||||
import android.content.IntentSender;
|
import android.content.IntentSender;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@@ -41,8 +41,9 @@ import com.google.android.gms.common.api.GoogleApiClient;
|
|||||||
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
|
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
|
||||||
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
|
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
|
||||||
import com.google.android.gms.common.api.ResultCallback;
|
import com.google.android.gms.common.api.ResultCallback;
|
||||||
import com.google.android.gms.common.data.FreezableUtils;
|
|
||||||
import com.google.android.gms.wearable.Asset;
|
import com.google.android.gms.wearable.Asset;
|
||||||
|
import com.google.android.gms.wearable.CapabilityApi;
|
||||||
|
import com.google.android.gms.wearable.CapabilityInfo;
|
||||||
import com.google.android.gms.wearable.DataApi;
|
import com.google.android.gms.wearable.DataApi;
|
||||||
import com.google.android.gms.wearable.DataApi.DataItemResult;
|
import com.google.android.gms.wearable.DataApi.DataItemResult;
|
||||||
import com.google.android.gms.wearable.DataEvent;
|
import com.google.android.gms.wearable.DataEvent;
|
||||||
@@ -61,7 +62,6 @@ import java.io.IOException;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||||
@@ -72,17 +72,20 @@ import java.util.concurrent.TimeUnit;
|
|||||||
* item every second while it is open. Also allows user to take a photo and send that as an asset
|
* item every second while it is open. Also allows user to take a photo and send that as an asset
|
||||||
* to the paired wearable.
|
* to the paired wearable.
|
||||||
*/
|
*/
|
||||||
public class MainActivity extends Activity implements DataApi.DataListener,
|
public class MainActivity extends Activity implements
|
||||||
MessageApi.MessageListener, NodeApi.NodeListener, ConnectionCallbacks,
|
CapabilityApi.CapabilityListener,
|
||||||
|
MessageApi.MessageListener,
|
||||||
|
DataApi.DataListener,
|
||||||
|
ConnectionCallbacks,
|
||||||
OnConnectionFailedListener {
|
OnConnectionFailedListener {
|
||||||
|
|
||||||
private static final String TAG = "MainActivity";
|
private static final String TAG = "MainActivity";
|
||||||
|
|
||||||
/**
|
//Request code for launching the Intent to resolve Google Play services errors.
|
||||||
* Request code for launching the Intent to resolve Google Play services errors.
|
|
||||||
*/
|
|
||||||
private static final int REQUEST_RESOLVE_ERROR = 1000;
|
private static final int REQUEST_RESOLVE_ERROR = 1000;
|
||||||
|
|
||||||
|
private static final int REQUEST_IMAGE_CAPTURE = 1;
|
||||||
|
|
||||||
private static final String START_ACTIVITY_PATH = "/start-activity";
|
private static final String START_ACTIVITY_PATH = "/start-activity";
|
||||||
private static final String COUNT_PATH = "/count";
|
private static final String COUNT_PATH = "/count";
|
||||||
private static final String IMAGE_PATH = "/image";
|
private static final String IMAGE_PATH = "/image";
|
||||||
@@ -100,18 +103,14 @@ public class MainActivity extends Activity implements DataApi.DataListener,
|
|||||||
private View mStartActivityBtn;
|
private View mStartActivityBtn;
|
||||||
|
|
||||||
private DataItemAdapter mDataItemListAdapter;
|
private DataItemAdapter mDataItemListAdapter;
|
||||||
private Handler mHandler;
|
|
||||||
|
|
||||||
// Send DataItems.
|
// Send DataItems.
|
||||||
private ScheduledExecutorService mGeneratorExecutor;
|
private ScheduledExecutorService mGeneratorExecutor;
|
||||||
private ScheduledFuture<?> mDataItemGeneratorFuture;
|
private ScheduledFuture<?> mDataItemGeneratorFuture;
|
||||||
|
|
||||||
static final int REQUEST_IMAGE_CAPTURE = 1;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle b) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(b);
|
super.onCreate(savedInstanceState);
|
||||||
mHandler = new Handler();
|
|
||||||
LOGD(TAG, "onCreate");
|
LOGD(TAG, "onCreate");
|
||||||
mCameraSupported = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
|
mCameraSupported = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
|
||||||
setContentView(R.layout.main_activity);
|
setContentView(R.layout.main_activity);
|
||||||
@@ -130,15 +129,6 @@ public class MainActivity extends Activity implements DataApi.DataListener,
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
||||||
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
|
|
||||||
Bundle extras = data.getExtras();
|
|
||||||
mImageBitmap = (Bitmap) extras.get("data");
|
|
||||||
mThumbView.setImageBitmap(mImageBitmap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStart() {
|
protected void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
@@ -162,16 +152,25 @@ public class MainActivity extends Activity implements DataApi.DataListener,
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStop() {
|
protected void onStop() {
|
||||||
if (!mResolvingError) {
|
if (!mResolvingError && (mGoogleApiClient != null) && (mGoogleApiClient.isConnected())) {
|
||||||
Wearable.DataApi.removeListener(mGoogleApiClient, this);
|
Wearable.DataApi.removeListener(mGoogleApiClient, this);
|
||||||
Wearable.MessageApi.removeListener(mGoogleApiClient, this);
|
Wearable.MessageApi.removeListener(mGoogleApiClient, this);
|
||||||
Wearable.NodeApi.removeListener(mGoogleApiClient, this);
|
Wearable.CapabilityApi.removeListener(mGoogleApiClient, this);
|
||||||
mGoogleApiClient.disconnect();
|
mGoogleApiClient.disconnect();
|
||||||
}
|
}
|
||||||
super.onStop();
|
super.onStop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override //ConnectionCallbacks
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
|
||||||
|
Bundle extras = data.getExtras();
|
||||||
|
mImageBitmap = (Bitmap) extras.get("data");
|
||||||
|
mThumbView.setImageBitmap(mImageBitmap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void onConnected(Bundle connectionHint) {
|
public void onConnected(Bundle connectionHint) {
|
||||||
LOGD(TAG, "Google API Client was connected");
|
LOGD(TAG, "Google API Client was connected");
|
||||||
mResolvingError = false;
|
mResolvingError = false;
|
||||||
@@ -179,157 +178,100 @@ public class MainActivity extends Activity implements DataApi.DataListener,
|
|||||||
mSendPhotoBtn.setEnabled(mCameraSupported);
|
mSendPhotoBtn.setEnabled(mCameraSupported);
|
||||||
Wearable.DataApi.addListener(mGoogleApiClient, this);
|
Wearable.DataApi.addListener(mGoogleApiClient, this);
|
||||||
Wearable.MessageApi.addListener(mGoogleApiClient, this);
|
Wearable.MessageApi.addListener(mGoogleApiClient, this);
|
||||||
Wearable.NodeApi.addListener(mGoogleApiClient, this);
|
Wearable.CapabilityApi.addListener(
|
||||||
|
mGoogleApiClient, this, Uri.parse("wear://"), CapabilityApi.FILTER_REACHABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override //ConnectionCallbacks
|
@Override
|
||||||
public void onConnectionSuspended(int cause) {
|
public void onConnectionSuspended(int cause) {
|
||||||
LOGD(TAG, "Connection to Google API client was suspended");
|
LOGD(TAG, "Connection to Google API client was suspended");
|
||||||
mStartActivityBtn.setEnabled(false);
|
mStartActivityBtn.setEnabled(false);
|
||||||
mSendPhotoBtn.setEnabled(false);
|
mSendPhotoBtn.setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override //OnConnectionFailedListener
|
@Override
|
||||||
public void onConnectionFailed(ConnectionResult result) {
|
public void onConnectionFailed(ConnectionResult result) {
|
||||||
if (mResolvingError) {
|
if (!mResolvingError) {
|
||||||
// Already attempting to resolve an error.
|
|
||||||
return;
|
if (result.hasResolution()) {
|
||||||
} else if (result.hasResolution()) {
|
try {
|
||||||
try {
|
mResolvingError = true;
|
||||||
mResolvingError = true;
|
result.startResolutionForResult(this, REQUEST_RESOLVE_ERROR);
|
||||||
result.startResolutionForResult(this, REQUEST_RESOLVE_ERROR);
|
} catch (IntentSender.SendIntentException e) {
|
||||||
} catch (IntentSender.SendIntentException e) {
|
// There was an error with the resolution intent. Try again.
|
||||||
// There was an error with the resolution intent. Try again.
|
mGoogleApiClient.connect();
|
||||||
mGoogleApiClient.connect();
|
}
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Connection to Google API client has failed");
|
||||||
|
mResolvingError = false;
|
||||||
|
mStartActivityBtn.setEnabled(false);
|
||||||
|
mSendPhotoBtn.setEnabled(false);
|
||||||
|
Wearable.DataApi.removeListener(mGoogleApiClient, this);
|
||||||
|
Wearable.MessageApi.removeListener(mGoogleApiClient, this);
|
||||||
|
Wearable.CapabilityApi.removeListener(mGoogleApiClient, this);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Log.e(TAG, "Connection to Google API client has failed");
|
|
||||||
mResolvingError = false;
|
|
||||||
mStartActivityBtn.setEnabled(false);
|
|
||||||
mSendPhotoBtn.setEnabled(false);
|
|
||||||
Wearable.DataApi.removeListener(mGoogleApiClient, this);
|
|
||||||
Wearable.MessageApi.removeListener(mGoogleApiClient, this);
|
|
||||||
Wearable.NodeApi.removeListener(mGoogleApiClient, this);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override //DataListener
|
@Override
|
||||||
public void onDataChanged(DataEventBuffer dataEvents) {
|
public void onDataChanged(DataEventBuffer dataEvents) {
|
||||||
LOGD(TAG, "onDataChanged: " + dataEvents);
|
LOGD(TAG, "onDataChanged: " + dataEvents);
|
||||||
// Need to freeze the dataEvents so they will exist later on the UI thread
|
|
||||||
final List<DataEvent> events = FreezableUtils.freezeIterable(dataEvents);
|
for (DataEvent event : dataEvents) {
|
||||||
runOnUiThread(new Runnable() {
|
if (event.getType() == DataEvent.TYPE_CHANGED) {
|
||||||
@Override
|
mDataItemListAdapter.add(
|
||||||
public void run() {
|
new Event("DataItem Changed", event.getDataItem().toString()));
|
||||||
for (DataEvent event : events) {
|
} else if (event.getType() == DataEvent.TYPE_DELETED) {
|
||||||
if (event.getType() == DataEvent.TYPE_CHANGED) {
|
mDataItemListAdapter.add(
|
||||||
mDataItemListAdapter.add(
|
new Event("DataItem Deleted", event.getDataItem().toString()));
|
||||||
new Event("DataItem Changed", event.getDataItem().toString()));
|
|
||||||
} else if (event.getType() == DataEvent.TYPE_DELETED) {
|
|
||||||
mDataItemListAdapter.add(
|
|
||||||
new Event("DataItem Deleted", event.getDataItem().toString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override //MessageListener
|
@Override
|
||||||
public void onMessageReceived(final MessageEvent messageEvent) {
|
public void onMessageReceived(final MessageEvent messageEvent) {
|
||||||
LOGD(TAG, "onMessageReceived() A message from watch was received:" + messageEvent
|
LOGD(TAG, "onMessageReceived() A message from watch was received:"
|
||||||
.getRequestId() + " " + messageEvent.getPath());
|
+ messageEvent.getRequestId() + " " + messageEvent.getPath());
|
||||||
mHandler.post(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
mDataItemListAdapter.add(new Event("Message from watch", messageEvent.toString()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
mDataItemListAdapter.add(new Event("Message from watch", messageEvent.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override //NodeListener
|
@Override
|
||||||
public void onPeerConnected(final Node peer) {
|
public void onCapabilityChanged(final CapabilityInfo capabilityInfo) {
|
||||||
LOGD(TAG, "onPeerConnected: " + peer);
|
LOGD(TAG, "onCapabilityChanged: " + capabilityInfo);
|
||||||
mHandler.post(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
mDataItemListAdapter.add(new Event("Connected", peer.toString()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
mDataItemListAdapter.add(new Event("onCapabilityChanged", capabilityInfo.toString()));
|
||||||
|
|
||||||
@Override //NodeListener
|
|
||||||
public void onPeerDisconnected(final Node peer) {
|
|
||||||
LOGD(TAG, "onPeerDisconnected: " + peer);
|
|
||||||
mHandler.post(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
mDataItemListAdapter.add(new Event("Disconnected", peer.toString()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A View Adapter for presenting the Event objects in a list
|
* Sets up UI components and their callback handlers.
|
||||||
*/
|
*/
|
||||||
private static class DataItemAdapter extends ArrayAdapter<Event> {
|
private void setupViews() {
|
||||||
|
mSendPhotoBtn = (Button) findViewById(R.id.sendPhoto);
|
||||||
|
mThumbView = (ImageView) findViewById(R.id.imageView);
|
||||||
|
mDataItemList = (ListView) findViewById(R.id.data_item_list);
|
||||||
|
mStartActivityBtn = findViewById(R.id.start_wearable_activity);
|
||||||
|
}
|
||||||
|
|
||||||
private final Context mContext;
|
public void onTakePhotoClick(View view) {
|
||||||
|
dispatchTakePictureIntent();
|
||||||
|
}
|
||||||
|
|
||||||
public DataItemAdapter(Context context, int unusedResource) {
|
public void onSendPhotoClick(View view) {
|
||||||
super(context, unusedResource);
|
if (null != mImageBitmap && mGoogleApiClient.isConnected()) {
|
||||||
mContext = context;
|
sendPhoto(toAsset(mImageBitmap));
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View getView(int position, View convertView, ViewGroup parent) {
|
|
||||||
ViewHolder holder;
|
|
||||||
if (convertView == null) {
|
|
||||||
holder = new ViewHolder();
|
|
||||||
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
|
|
||||||
Context.LAYOUT_INFLATER_SERVICE);
|
|
||||||
convertView = inflater.inflate(android.R.layout.two_line_list_item, null);
|
|
||||||
convertView.setTag(holder);
|
|
||||||
holder.text1 = (TextView) convertView.findViewById(android.R.id.text1);
|
|
||||||
holder.text2 = (TextView) convertView.findViewById(android.R.id.text2);
|
|
||||||
} else {
|
|
||||||
holder = (ViewHolder) convertView.getTag();
|
|
||||||
}
|
|
||||||
Event event = getItem(position);
|
|
||||||
holder.text1.setText(event.title);
|
|
||||||
holder.text2.setText(event.text);
|
|
||||||
return convertView;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ViewHolder {
|
|
||||||
|
|
||||||
TextView text1;
|
|
||||||
TextView text2;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Event {
|
/**
|
||||||
|
* Sends an RPC to start a fullscreen Activity on the wearable.
|
||||||
|
*/
|
||||||
|
public void onStartWearableActivityClick(View view) {
|
||||||
|
LOGD(TAG, "Generating RPC");
|
||||||
|
|
||||||
String title;
|
// Trigger an AsyncTask that will query for a list of connected nodes and send a
|
||||||
String text;
|
// "start-activity" message to each connected node.
|
||||||
|
new StartWearableActivityTask().execute();
|
||||||
public Event(String title, String text) {
|
|
||||||
this.title = title;
|
|
||||||
this.text = text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Collection<String> getNodes() {
|
|
||||||
HashSet<String> results = new HashSet<>();
|
|
||||||
NodeApi.GetConnectedNodesResult nodes =
|
|
||||||
Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).await();
|
|
||||||
|
|
||||||
for (Node node : nodes.getNodes()) {
|
|
||||||
results.add(node.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendStartActivityMessage(String node) {
|
private void sendStartActivityMessage(String node) {
|
||||||
@@ -347,61 +289,6 @@ public class MainActivity extends Activity implements DataApi.DataListener,
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class StartWearableActivityTask extends AsyncTask<Void, Void, Void> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(Void... args) {
|
|
||||||
Collection<String> nodes = getNodes();
|
|
||||||
for (String node : nodes) {
|
|
||||||
sendStartActivityMessage(node);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends an RPC to start a fullscreen Activity on the wearable.
|
|
||||||
*/
|
|
||||||
public void onStartWearableActivityClick(View view) {
|
|
||||||
LOGD(TAG, "Generating RPC");
|
|
||||||
|
|
||||||
// Trigger an AsyncTask that will query for a list of connected nodes and send a
|
|
||||||
// "start-activity" message to each connected node.
|
|
||||||
new StartWearableActivityTask().execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a DataItem based on an incrementing count.
|
|
||||||
*/
|
|
||||||
private class DataItemGenerator implements Runnable {
|
|
||||||
|
|
||||||
private int count = 0;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(COUNT_PATH);
|
|
||||||
putDataMapRequest.getDataMap().putInt(COUNT_KEY, count++);
|
|
||||||
putDataMapRequest.setUrgent();
|
|
||||||
PutDataRequest request = putDataMapRequest.asPutDataRequest();
|
|
||||||
request.setUrgent();
|
|
||||||
|
|
||||||
LOGD(TAG, "Generating DataItem: " + request);
|
|
||||||
if (!mGoogleApiClient.isConnected()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Wearable.DataApi.putDataItem(mGoogleApiClient, request)
|
|
||||||
.setResultCallback(new ResultCallback<DataItemResult>() {
|
|
||||||
@Override
|
|
||||||
public void onResult(DataItemResult dataItemResult) {
|
|
||||||
if (!dataItemResult.getStatus().isSuccess()) {
|
|
||||||
Log.e(TAG, "ERROR: failed to putDataItem, status code: "
|
|
||||||
+ dataItemResult.getStatus().getStatusCode());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatches an {@link android.content.Intent} to take a photo. Result will be returned back
|
* Dispatches an {@link android.content.Intent} to take a photo. Result will be returned back
|
||||||
* in onActivityResult().
|
* in onActivityResult().
|
||||||
@@ -437,7 +324,7 @@ public class MainActivity extends Activity implements DataApi.DataListener,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends the asset that was created form the photo we took by adding it to the Data Item store.
|
* Sends the asset that was created from the photo we took by adding it to the Data Item store.
|
||||||
*/
|
*/
|
||||||
private void sendPhoto(Asset asset) {
|
private void sendPhoto(Asset asset) {
|
||||||
PutDataMapRequest dataMap = PutDataMapRequest.create(IMAGE_PATH);
|
PutDataMapRequest dataMap = PutDataMapRequest.create(IMAGE_PATH);
|
||||||
@@ -454,27 +341,18 @@ public class MainActivity extends Activity implements DataApi.DataListener,
|
|||||||
.isSuccess());
|
.isSuccess());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onTakePhotoClick(View view) {
|
private Collection<String> getNodes() {
|
||||||
dispatchTakePictureIntent();
|
HashSet<String> results = new HashSet<>();
|
||||||
}
|
NodeApi.GetConnectedNodesResult nodes =
|
||||||
|
Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).await();
|
||||||
|
|
||||||
public void onSendPhotoClick(View view) {
|
for (Node node : nodes.getNodes()) {
|
||||||
if (null != mImageBitmap && mGoogleApiClient.isConnected()) {
|
results.add(node.getId());
|
||||||
sendPhoto(toAsset(mImageBitmap));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
return results;
|
||||||
* Sets up UI components and their callback handlers.
|
|
||||||
*/
|
|
||||||
private void setupViews() {
|
|
||||||
mSendPhotoBtn = (Button) findViewById(R.id.sendPhoto);
|
|
||||||
mThumbView = (ImageView) findViewById(R.id.imageView);
|
|
||||||
mDataItemList = (ListView) findViewById(R.id.data_item_list);
|
|
||||||
mStartActivityBtn = findViewById(R.id.start_wearable_activity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -486,4 +364,96 @@ public class MainActivity extends Activity implements DataApi.DataListener,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A View Adapter for presenting the Event objects in a list
|
||||||
|
*/
|
||||||
|
private static class DataItemAdapter extends ArrayAdapter<Event> {
|
||||||
|
|
||||||
|
private final Context mContext;
|
||||||
|
|
||||||
|
public DataItemAdapter(Context context, int unusedResource) {
|
||||||
|
super(context, unusedResource);
|
||||||
|
mContext = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
|
ViewHolder holder;
|
||||||
|
if (convertView == null) {
|
||||||
|
holder = new ViewHolder();
|
||||||
|
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
|
||||||
|
Context.LAYOUT_INFLATER_SERVICE);
|
||||||
|
convertView = inflater.inflate(android.R.layout.two_line_list_item, null);
|
||||||
|
convertView.setTag(holder);
|
||||||
|
holder.text1 = (TextView) convertView.findViewById(android.R.id.text1);
|
||||||
|
holder.text2 = (TextView) convertView.findViewById(android.R.id.text2);
|
||||||
|
} else {
|
||||||
|
holder = (ViewHolder) convertView.getTag();
|
||||||
|
}
|
||||||
|
Event event = getItem(position);
|
||||||
|
holder.text1.setText(event.title);
|
||||||
|
holder.text2.setText(event.text);
|
||||||
|
return convertView;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ViewHolder {
|
||||||
|
TextView text1;
|
||||||
|
TextView text2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Event {
|
||||||
|
|
||||||
|
String title;
|
||||||
|
String text;
|
||||||
|
|
||||||
|
public Event(String title, String text) {
|
||||||
|
this.title = title;
|
||||||
|
this.text = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class StartWearableActivityTask extends AsyncTask<Void, Void, Void> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... args) {
|
||||||
|
Collection<String> nodes = getNodes();
|
||||||
|
for (String node : nodes) {
|
||||||
|
sendStartActivityMessage(node);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a DataItem based on an incrementing count.
|
||||||
|
*/
|
||||||
|
private class DataItemGenerator implements Runnable {
|
||||||
|
|
||||||
|
private int count = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(COUNT_PATH);
|
||||||
|
putDataMapRequest.getDataMap().putInt(COUNT_KEY, count++);
|
||||||
|
|
||||||
|
PutDataRequest request = putDataMapRequest.asPutDataRequest();
|
||||||
|
request.setUrgent();
|
||||||
|
|
||||||
|
LOGD(TAG, "Generating DataItem: " + request);
|
||||||
|
if (!mGoogleApiClient.isConnected()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Wearable.DataApi.putDataItem(mGoogleApiClient, request)
|
||||||
|
.setResultCallback(new ResultCallback<DataItemResult>() {
|
||||||
|
@Override
|
||||||
|
public void onResult(DataItemResult dataItemResult) {
|
||||||
|
if (!dataItemResult.getStatus().isSuccess()) {
|
||||||
|
Log.e(TAG, "ERROR: failed to putDataItem, status code: "
|
||||||
|
+ dataItemResult.getStatus().getStatusCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
package="com.example.android.wearable.datalayer" >
|
package="com.example.android.wearable.datalayer" >
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="20"
|
<uses-sdk android:minSdkVersion="20"
|
||||||
android:targetSdkVersion="22" />
|
android:targetSdkVersion="23" />
|
||||||
|
|
||||||
<uses-feature android:name="android.hardware.type.watch" />
|
<uses-feature android:name="android.hardware.type.watch" />
|
||||||
|
|
||||||
@@ -35,10 +35,15 @@
|
|||||||
<service
|
<service
|
||||||
android:name=".DataLayerListenerService" >
|
android:name=".DataLayerListenerService" >
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
|
<!-- listeners receive events that match the action and data filters -->
|
||||||
|
<action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
|
||||||
|
<data android:scheme="wear" android:host="*" android:pathPrefix="/count"/>
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED" />
|
||||||
|
<data android:scheme="wear" android:host="*" android:pathPrefix="/start-activity"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:label="@string/app_name">
|
android:label="@string/app_name">
|
||||||
|
|||||||
@@ -25,11 +25,9 @@ import com.google.android.gms.common.api.GoogleApiClient;
|
|||||||
import com.google.android.gms.wearable.DataEvent;
|
import com.google.android.gms.wearable.DataEvent;
|
||||||
import com.google.android.gms.wearable.DataEventBuffer;
|
import com.google.android.gms.wearable.DataEventBuffer;
|
||||||
import com.google.android.gms.wearable.MessageEvent;
|
import com.google.android.gms.wearable.MessageEvent;
|
||||||
import com.google.android.gms.wearable.Node;
|
|
||||||
import com.google.android.gms.wearable.Wearable;
|
import com.google.android.gms.wearable.Wearable;
|
||||||
import com.google.android.gms.wearable.WearableListenerService;
|
import com.google.android.gms.wearable.WearableListenerService;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,7 +35,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
*/
|
*/
|
||||||
public class DataLayerListenerService extends WearableListenerService {
|
public class DataLayerListenerService extends WearableListenerService {
|
||||||
|
|
||||||
private static final String TAG = "DataLayerListenerServic";
|
private static final String TAG = "DataLayerService";
|
||||||
|
|
||||||
private static final String START_ACTIVITY_PATH = "/start-activity";
|
private static final String START_ACTIVITY_PATH = "/start-activity";
|
||||||
private static final String DATA_ITEM_RECEIVED_PATH = "/data-item-received";
|
private static final String DATA_ITEM_RECEIVED_PATH = "/data-item-received";
|
||||||
@@ -98,16 +96,6 @@ public class DataLayerListenerService extends WearableListenerService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPeerConnected(Node peer) {
|
|
||||||
LOGD(TAG, "onPeerConnected: " + peer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPeerDisconnected(Node peer) {
|
|
||||||
LOGD(TAG, "onPeerDisconnected: " + peer);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void LOGD(final String tag, String message) {
|
public static void LOGD(final String tag, String message) {
|
||||||
if (Log.isLoggable(tag, Log.DEBUG)) {
|
if (Log.isLoggable(tag, Log.DEBUG)) {
|
||||||
Log.d(tag, message);
|
Log.d(tag, message);
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ import android.app.Fragment;
|
|||||||
import android.app.FragmentManager;
|
import android.app.FragmentManager;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
|
||||||
import android.support.wearable.view.DotsPageIndicator;
|
import android.support.wearable.view.DotsPageIndicator;
|
||||||
import android.support.wearable.view.FragmentGridPagerAdapter;
|
import android.support.wearable.view.FragmentGridPagerAdapter;
|
||||||
import android.support.wearable.view.GridViewPager;
|
import android.support.wearable.view.GridViewPager;
|
||||||
@@ -54,7 +54,6 @@ import com.google.android.gms.wearable.DataMapItem;
|
|||||||
import com.google.android.gms.wearable.MessageApi;
|
import com.google.android.gms.wearable.MessageApi;
|
||||||
import com.google.android.gms.wearable.MessageEvent;
|
import com.google.android.gms.wearable.MessageEvent;
|
||||||
import com.google.android.gms.wearable.Node;
|
import com.google.android.gms.wearable.Node;
|
||||||
import com.google.android.gms.wearable.NodeApi;
|
|
||||||
import com.google.android.gms.wearable.Wearable;
|
import com.google.android.gms.wearable.Wearable;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -78,9 +77,12 @@ import java.util.Set;
|
|||||||
* </li>
|
* </li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public class MainActivity extends Activity implements ConnectionCallbacks,
|
public class MainActivity extends Activity implements
|
||||||
OnConnectionFailedListener, DataApi.DataListener, MessageApi.MessageListener,
|
ConnectionCallbacks,
|
||||||
NodeApi.NodeListener {
|
OnConnectionFailedListener,
|
||||||
|
DataApi.DataListener,
|
||||||
|
MessageApi.MessageListener,
|
||||||
|
CapabilityApi.CapabilityListener {
|
||||||
|
|
||||||
private static final String TAG = "MainActivity";
|
private static final String TAG = "MainActivity";
|
||||||
private static final String CAPABILITY_1_NAME = "capability_1";
|
private static final String CAPABILITY_1_NAME = "capability_1";
|
||||||
@@ -92,8 +94,8 @@ public class MainActivity extends Activity implements ConnectionCallbacks,
|
|||||||
private AssetFragment mAssetFragment;
|
private AssetFragment mAssetFragment;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle b) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(b);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.main_activity);
|
setContentView(R.layout.main_activity);
|
||||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||||
setupViews();
|
setupViews();
|
||||||
@@ -112,11 +114,14 @@ public class MainActivity extends Activity implements ConnectionCallbacks,
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPause() {
|
protected void onPause() {
|
||||||
|
if ((mGoogleApiClient != null) && mGoogleApiClient.isConnected()) {
|
||||||
|
Wearable.DataApi.removeListener(mGoogleApiClient, this);
|
||||||
|
Wearable.MessageApi.removeListener(mGoogleApiClient, this);
|
||||||
|
Wearable.CapabilityApi.removeListener(mGoogleApiClient, this);
|
||||||
|
mGoogleApiClient.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
super.onPause();
|
super.onPause();
|
||||||
Wearable.DataApi.removeListener(mGoogleApiClient, this);
|
|
||||||
Wearable.MessageApi.removeListener(mGoogleApiClient, this);
|
|
||||||
Wearable.NodeApi.removeListener(mGoogleApiClient, this);
|
|
||||||
mGoogleApiClient.disconnect();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -124,7 +129,8 @@ public class MainActivity extends Activity implements ConnectionCallbacks,
|
|||||||
LOGD(TAG, "onConnected(): Successfully connected to Google API client");
|
LOGD(TAG, "onConnected(): Successfully connected to Google API client");
|
||||||
Wearable.DataApi.addListener(mGoogleApiClient, this);
|
Wearable.DataApi.addListener(mGoogleApiClient, this);
|
||||||
Wearable.MessageApi.addListener(mGoogleApiClient, this);
|
Wearable.MessageApi.addListener(mGoogleApiClient, this);
|
||||||
Wearable.NodeApi.addListener(mGoogleApiClient, this);
|
Wearable.CapabilityApi.addListener(
|
||||||
|
mGoogleApiClient, this, Uri.parse("wear://"), CapabilityApi.FILTER_REACHABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -244,13 +250,9 @@ public class MainActivity extends Activity implements ConnectionCallbacks,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPeerConnected(Node node) {
|
public void onCapabilityChanged(CapabilityInfo capabilityInfo) {
|
||||||
mDataFragment.appendItem("Node Connected", node.getId());
|
LOGD(TAG, "onCapabilityChanged: " + capabilityInfo);
|
||||||
}
|
mDataFragment.appendItem("onCapabilityChanged", capabilityInfo.toString());
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPeerDisconnected(Node node) {
|
|
||||||
mDataFragment.appendItem("Node Disconnected", node.getId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupViews() {
|
private void setupViews() {
|
||||||
@@ -312,7 +314,7 @@ public class MainActivity extends Activity implements ConnectionCallbacks,
|
|||||||
@Override
|
@Override
|
||||||
protected Bitmap doInBackground(Asset... params) {
|
protected Bitmap doInBackground(Asset... params) {
|
||||||
|
|
||||||
if(params.length > 0) {
|
if (params.length > 0) {
|
||||||
|
|
||||||
Asset asset = params[0];
|
Asset asset = params[0];
|
||||||
|
|
||||||
@@ -334,7 +336,7 @@ public class MainActivity extends Activity implements ConnectionCallbacks,
|
|||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(Bitmap bitmap) {
|
protected void onPostExecute(Bitmap bitmap) {
|
||||||
|
|
||||||
if(bitmap != null) {
|
if (bitmap != null) {
|
||||||
LOGD(TAG, "Setting background image on second page..");
|
LOGD(TAG, "Setting background image on second page..");
|
||||||
moveToPage(1);
|
moveToPage(1);
|
||||||
mAssetFragment.setBackgroundImage(bitmap);
|
mAssetFragment.setBackgroundImage(bitmap);
|
||||||
|
|||||||
@@ -36,49 +36,57 @@ public class CameraHelper {
|
|||||||
public static final int MEDIA_TYPE_VIDEO = 2;
|
public static final int MEDIA_TYPE_VIDEO = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Iterate over supported camera preview sizes to see which one best fits the
|
* Iterate over supported camera video sizes to see which one best fits the
|
||||||
* dimensions of the given view while maintaining the aspect ratio. If none can,
|
* dimensions of the given view while maintaining the aspect ratio. If none can,
|
||||||
* be lenient with the aspect ratio.
|
* be lenient with the aspect ratio.
|
||||||
*
|
*
|
||||||
* @param sizes Supported camera preview sizes.
|
* @param supportedVideoSizes Supported camera video sizes.
|
||||||
* @param w The width of the view.
|
* @param previewSizes Supported camera preview sizes.
|
||||||
* @param h The height of the view.
|
* @param w The width of the view.
|
||||||
* @return Best match camera preview size to fit in the view.
|
* @param h The height of the view.
|
||||||
|
* @return Best match camera video size to fit in the view.
|
||||||
*/
|
*/
|
||||||
public static Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
|
public static Camera.Size getOptimalVideoSize(List<Camera.Size> supportedVideoSizes,
|
||||||
|
List<Camera.Size> previewSizes, int w, int h) {
|
||||||
// Use a very small tolerance because we want an exact match.
|
// Use a very small tolerance because we want an exact match.
|
||||||
final double ASPECT_TOLERANCE = 0.1;
|
final double ASPECT_TOLERANCE = 0.1;
|
||||||
double targetRatio = (double) w / h;
|
double targetRatio = (double) w / h;
|
||||||
if (sizes == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
|
// Supported video sizes list might be null, it means that we are allowed to use the preview
|
||||||
|
// sizes
|
||||||
|
List<Camera.Size> videoSizes;
|
||||||
|
if (supportedVideoSizes != null) {
|
||||||
|
videoSizes = supportedVideoSizes;
|
||||||
|
} else {
|
||||||
|
videoSizes = previewSizes;
|
||||||
|
}
|
||||||
Camera.Size optimalSize = null;
|
Camera.Size optimalSize = null;
|
||||||
|
|
||||||
// Start with max value and refine as we iterate over available preview sizes. This is the
|
// Start with max value and refine as we iterate over available video sizes. This is the
|
||||||
// minimum difference between view and camera height.
|
// minimum difference between view and camera height.
|
||||||
double minDiff = Double.MAX_VALUE;
|
double minDiff = Double.MAX_VALUE;
|
||||||
|
|
||||||
// Target view height
|
// Target view height
|
||||||
int targetHeight = h;
|
int targetHeight = h;
|
||||||
|
|
||||||
// Try to find a preview size that matches aspect ratio and the target view size.
|
// Try to find a video size that matches aspect ratio and the target view size.
|
||||||
// Iterate over all available sizes and pick the largest size that can fit in the view and
|
// Iterate over all available sizes and pick the largest size that can fit in the view and
|
||||||
// still maintain the aspect ratio.
|
// still maintain the aspect ratio.
|
||||||
for (Camera.Size size : sizes) {
|
for (Camera.Size size : videoSizes) {
|
||||||
double ratio = (double) size.width / size.height;
|
double ratio = (double) size.width / size.height;
|
||||||
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
|
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
|
||||||
continue;
|
continue;
|
||||||
if (Math.abs(size.height - targetHeight) < minDiff) {
|
if (Math.abs(size.height - targetHeight) < minDiff && previewSizes.contains(size)) {
|
||||||
optimalSize = size;
|
optimalSize = size;
|
||||||
minDiff = Math.abs(size.height - targetHeight);
|
minDiff = Math.abs(size.height - targetHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cannot find preview size that matches the aspect ratio, ignore the requirement
|
// Cannot find video size that matches the aspect ratio, ignore the requirement
|
||||||
if (optimalSize == null) {
|
if (optimalSize == null) {
|
||||||
minDiff = Double.MAX_VALUE;
|
minDiff = Double.MAX_VALUE;
|
||||||
for (Camera.Size size : sizes) {
|
for (Camera.Size size : videoSizes) {
|
||||||
if (Math.abs(size.height - targetHeight) < minDiff) {
|
if (Math.abs(size.height - targetHeight) < minDiff && previewSizes.contains(size)) {
|
||||||
optimalSize = size;
|
optimalSize = size;
|
||||||
minDiff = Math.abs(size.height - targetHeight);
|
minDiff = Math.abs(size.height - targetHeight);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import android.widget.Button;
|
|||||||
|
|
||||||
import com.example.android.common.media.CameraHelper;
|
import com.example.android.common.media.CameraHelper;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -45,6 +46,7 @@ public class MainActivity extends Activity {
|
|||||||
private Camera mCamera;
|
private Camera mCamera;
|
||||||
private TextureView mPreview;
|
private TextureView mPreview;
|
||||||
private MediaRecorder mMediaRecorder;
|
private MediaRecorder mMediaRecorder;
|
||||||
|
private File mOutputFile;
|
||||||
|
|
||||||
private boolean isRecording = false;
|
private boolean isRecording = false;
|
||||||
private static final String TAG = "Recorder";
|
private static final String TAG = "Recorder";
|
||||||
@@ -71,7 +73,15 @@ public class MainActivity extends Activity {
|
|||||||
// BEGIN_INCLUDE(stop_release_media_recorder)
|
// BEGIN_INCLUDE(stop_release_media_recorder)
|
||||||
|
|
||||||
// stop recording and release camera
|
// stop recording and release camera
|
||||||
mMediaRecorder.stop(); // stop the recording
|
try {
|
||||||
|
mMediaRecorder.stop(); // stop the recording
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
// RuntimeException is thrown when stop() is called immediately after start().
|
||||||
|
// In this case the output file is not properly constructed ans should be deleted.
|
||||||
|
Log.d(TAG, "RuntimeException: stop() is called immediately after start()");
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
|
mOutputFile.delete();
|
||||||
|
}
|
||||||
releaseMediaRecorder(); // release the MediaRecorder object
|
releaseMediaRecorder(); // release the MediaRecorder object
|
||||||
mCamera.lock(); // take camera access back from MediaRecorder
|
mCamera.lock(); // take camera access back from MediaRecorder
|
||||||
|
|
||||||
@@ -137,8 +147,9 @@ public class MainActivity extends Activity {
|
|||||||
// dimensions of our preview surface.
|
// dimensions of our preview surface.
|
||||||
Camera.Parameters parameters = mCamera.getParameters();
|
Camera.Parameters parameters = mCamera.getParameters();
|
||||||
List<Camera.Size> mSupportedPreviewSizes = parameters.getSupportedPreviewSizes();
|
List<Camera.Size> mSupportedPreviewSizes = parameters.getSupportedPreviewSizes();
|
||||||
Camera.Size optimalSize = CameraHelper.getOptimalPreviewSize(mSupportedPreviewSizes,
|
List<Camera.Size> mSupportedVideoSizes = parameters.getSupportedVideoSizes();
|
||||||
mPreview.getWidth(), mPreview.getHeight());
|
Camera.Size optimalSize = CameraHelper.getOptimalVideoSize(mSupportedVideoSizes,
|
||||||
|
mSupportedPreviewSizes, mPreview.getWidth(), mPreview.getHeight());
|
||||||
|
|
||||||
// Use the same size for recording profile.
|
// Use the same size for recording profile.
|
||||||
CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
|
CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
|
||||||
@@ -174,8 +185,11 @@ public class MainActivity extends Activity {
|
|||||||
mMediaRecorder.setProfile(profile);
|
mMediaRecorder.setProfile(profile);
|
||||||
|
|
||||||
// Step 4: Set output file
|
// Step 4: Set output file
|
||||||
mMediaRecorder.setOutputFile(CameraHelper.getOutputMediaFile(
|
mOutputFile = CameraHelper.getOutputMediaFile(CameraHelper.MEDIA_TYPE_VIDEO);
|
||||||
CameraHelper.MEDIA_TYPE_VIDEO).toString());
|
if (mOutputFile == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mMediaRecorder.setOutputFile(mOutputFile.getPath());
|
||||||
// END_INCLUDE (configure_media_recorder)
|
// END_INCLUDE (configure_media_recorder)
|
||||||
|
|
||||||
// Step 5: Prepare configured MediaRecorder
|
// Step 5: Prepare configured MediaRecorder
|
||||||
|
|||||||
@@ -66,7 +66,8 @@
|
|||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="true" >
|
android:exported="true" >
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
|
<action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED" />
|
||||||
|
<data android:scheme="wear" android:host="*" android:pathPrefix="/"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
</application>
|
</application>
|
||||||
|
|||||||
@@ -61,7 +61,8 @@
|
|||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="true" >
|
android:exported="true" >
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
|
<action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED" />
|
||||||
|
<data android:scheme="wear" android:host="*" android:pathPrefix="/"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
</application>
|
</application>
|
||||||
|
|||||||
@@ -48,7 +48,10 @@
|
|||||||
</activity>
|
</activity>
|
||||||
<service android:name=".db.UpdateService">
|
<service android:name=".db.UpdateService">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
|
<!-- listeners receive events that match the action and data filters -->
|
||||||
|
<action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
|
||||||
|
<!-- filters by Constants.PATH. -->
|
||||||
|
<data android:scheme="wear" android:host="*" android:pathPrefix="/location"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
</application>
|
</application>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
package="com.example.android.wearable.speedtracker">
|
package="com.example.android.wearable.speedtracker">
|
||||||
|
|
||||||
<uses-feature android:name="android.hardware.type.watch"/>
|
<uses-feature android:name="android.hardware.type.watch"/>
|
||||||
<uses-feature android:name="android.hardware.location.gps" android:required="true" />
|
<uses-feature android:name="android.hardware.location.gps" android:required="false" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
<?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"
|
|
||||||
android:layout_width="match_parent" android:layout_height="match_parent">
|
|
||||||
<View
|
|
||||||
android:id="@+id/center"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_centerInParent="true"/>
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/textView"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_above="@id/center"
|
|
||||||
android:layout_marginBottom="18dp"
|
|
||||||
android:fontFamily="sans-serif-light"
|
|
||||||
android:textSize="18sp"
|
|
||||||
android:text="@string/start_saving_gps"/>
|
|
||||||
<android.support.wearable.view.CircledImageView
|
|
||||||
android:id="@+id/cancelBtn"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_below="@id/center"
|
|
||||||
android:layout_toLeftOf="@id/center"
|
|
||||||
android:layout_marginEnd="10dp"
|
|
||||||
android:src="@drawable/ic_cancel_80"
|
|
||||||
app:circle_color="@color/grey"
|
|
||||||
android:onClick="onClick"
|
|
||||||
app:circle_padding="@dimen/circle_padding"
|
|
||||||
app:circle_radius="@dimen/circle_radius"
|
|
||||||
app:circle_radius_pressed="@dimen/circle_radius_pressed" />
|
|
||||||
<android.support.wearable.view.CircledImageView
|
|
||||||
android:id="@+id/submitBtn"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_below="@id/center"
|
|
||||||
android:layout_toRightOf="@id/center"
|
|
||||||
android:layout_marginStart="10dp"
|
|
||||||
android:src="@drawable/ic_confirmation_80"
|
|
||||||
app:circle_color="@color/blue"
|
|
||||||
android:onClick="onClick"
|
|
||||||
app:circle_padding="@dimen/circle_padding"
|
|
||||||
app:circle_radius="@dimen/circle_radius"
|
|
||||||
app:circle_radius_pressed="@dimen/circle_radius_pressed" />
|
|
||||||
</RelativeLayout>
|
|
||||||
@@ -330,6 +330,13 @@ public class WearableMainActivity extends WearableActivity implements
|
|||||||
public void onConnected(Bundle bundle) {
|
public void onConnected(Bundle bundle) {
|
||||||
|
|
||||||
Log.d(TAG, "onConnected()");
|
Log.d(TAG, "onConnected()");
|
||||||
|
requestLocation();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requestLocation() {
|
||||||
|
Log.d(TAG, "requestLocation()");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* mGpsPermissionApproved covers 23+ (M+) style permissions. If that is already approved or
|
* mGpsPermissionApproved covers 23+ (M+) style permissions. If that is already approved or
|
||||||
@@ -464,6 +471,11 @@ public class WearableMainActivity extends WearableActivity implements
|
|||||||
&& (grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
|
&& (grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
|
||||||
Log.i(TAG, "GPS permission granted.");
|
Log.i(TAG, "GPS permission granted.");
|
||||||
mGpsPermissionApproved = true;
|
mGpsPermissionApproved = true;
|
||||||
|
|
||||||
|
if(mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
|
||||||
|
requestLocation();
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Log.i(TAG, "GPS permission NOT granted.");
|
Log.i(TAG, "GPS permission NOT granted.");
|
||||||
mGpsPermissionApproved = false;
|
mGpsPermissionApproved = false;
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2014 Google Inc. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.example.android.wearable.speedtracker.ui;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.example.android.wearable.speedtracker.R;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A simple activity that allows the user to start or stop recording of GPS location data.
|
|
||||||
*/
|
|
||||||
public class LocationSettingActivity extends Activity {
|
|
||||||
|
|
||||||
private static final String PREFS_KEY_SAVE_GPS = "save-gps";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.saving_activity);
|
|
||||||
TextView textView = (TextView) findViewById(R.id.textView);
|
|
||||||
textView.setText(getGpsRecordingStatusFromPreferences(this) ? R.string.stop_saving_gps
|
|
||||||
: R.string.start_saving_gps);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onClick(View view) {
|
|
||||||
switch (view.getId()) {
|
|
||||||
case R.id.submitBtn:
|
|
||||||
saveGpsRecordingStatusToPreferences(LocationSettingActivity.this,
|
|
||||||
!getGpsRecordingStatusFromPreferences(this));
|
|
||||||
break;
|
|
||||||
case R.id.cancelBtn:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the persisted value for whether the app should record the GPS location data or not. If
|
|
||||||
* there is no prior value persisted, it returns {@code false}.
|
|
||||||
*/
|
|
||||||
public static boolean getGpsRecordingStatusFromPreferences(Context context) {
|
|
||||||
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
|
|
||||||
return pref.getBoolean(PREFS_KEY_SAVE_GPS, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Persists the user selection to whether save the GPS location data or not.
|
|
||||||
*/
|
|
||||||
public static void saveGpsRecordingStatusToPreferences(Context context, boolean value) {
|
|
||||||
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
|
|
||||||
pref.edit().putBoolean(PREFS_KEY_SAVE_GPS, value).apply();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2013 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package com.example.android.storageprovider;
|
|
||||||
|
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.provider.DocumentsContract;
|
|
||||||
import android.support.v4.app.Fragment;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
|
|
||||||
import com.example.android.common.logger.Log;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggles the user's login status via a login menu option, and enables/disables the cloud storage
|
|
||||||
* content provider.
|
|
||||||
*/
|
|
||||||
public class MyCloudFragment extends Fragment {
|
|
||||||
|
|
||||||
private static final String TAG = "MyCloudFragment";
|
|
||||||
private static final String AUTHORITY = "com.example.android.storageprovider.documents";
|
|
||||||
private boolean mLoggedIn = false;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
mLoggedIn = readLoginValue();
|
|
||||||
|
|
||||||
setHasOptionsMenu(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPrepareOptionsMenu(Menu menu) {
|
|
||||||
super.onPrepareOptionsMenu(menu);
|
|
||||||
MenuItem item = menu.findItem(R.id.sample_action);
|
|
||||||
item.setTitle(mLoggedIn ? R.string.log_out : R.string.log_in);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
if (item.getItemId() == R.id.sample_action) {
|
|
||||||
toggleLogin();
|
|
||||||
item.setTitle(mLoggedIn ? R.string.log_out : R.string.log_in);
|
|
||||||
|
|
||||||
// BEGIN_INCLUDE(notify_change)
|
|
||||||
// Notify the system that the status of our roots has changed. This will trigger
|
|
||||||
// a call to MyCloudProvider.queryRoots() and force a refresh of the system
|
|
||||||
// picker UI. It's important to call this or stale results may persist.
|
|
||||||
getActivity().getContentResolver().notifyChange(DocumentsContract.buildRootsUri
|
|
||||||
(AUTHORITY), null, false);
|
|
||||||
// END_INCLUDE(notify_change)
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dummy function to change the user's authorization status.
|
|
||||||
*/
|
|
||||||
private void toggleLogin() {
|
|
||||||
// Replace this with your standard method of authentication to determine if your app
|
|
||||||
// should make the user's documents available.
|
|
||||||
mLoggedIn = !mLoggedIn;
|
|
||||||
writeLoginValue(mLoggedIn);
|
|
||||||
Log.i(TAG, getString(mLoggedIn ? R.string.logged_in_info : R.string.logged_out_info));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dummy function to save whether the user is logged in.
|
|
||||||
*/
|
|
||||||
private void writeLoginValue(boolean loggedIn) {
|
|
||||||
final SharedPreferences sharedPreferences =
|
|
||||||
getActivity().getSharedPreferences(getString(R.string.app_name),
|
|
||||||
getActivity().MODE_PRIVATE);
|
|
||||||
sharedPreferences.edit().putBoolean(getString(R.string.key_logged_in), loggedIn).commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dummy function to determine whether the user is logged in.
|
|
||||||
*/
|
|
||||||
private boolean readLoginValue() {
|
|
||||||
final SharedPreferences sharedPreferences =
|
|
||||||
getActivity().getSharedPreferences(getString(R.string.app_name),
|
|
||||||
getActivity().MODE_PRIVATE);
|
|
||||||
return sharedPreferences.getBoolean(getString(R.string.key_logged_in), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@@ -42,8 +42,10 @@
|
|||||||
</activity>
|
</activity>
|
||||||
<service android:name=".DismissListener">
|
<service android:name=".DismissListener">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action
|
<!-- listeners receive events that match the action and data filters -->
|
||||||
android:name="com.google.android.gms.wearable.BIND_LISTENER" />
|
<action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
|
||||||
|
<!-- filters by Constants.BOTH_PATH ('/both') -->
|
||||||
|
<data android:scheme="wear" android:host="*" android:pathPrefix="/both"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action
|
<action
|
||||||
|
|||||||
@@ -41,8 +41,13 @@
|
|||||||
</activity>
|
</activity>
|
||||||
<service android:name=".NotificationUpdateService">
|
<service android:name=".NotificationUpdateService">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action
|
<!-- listeners receive events that match the action and data filters -->
|
||||||
android:name="com.google.android.gms.wearable.BIND_LISTENER" />
|
<action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
|
||||||
|
|
||||||
|
<!-- filters by Constants.BOTH_PATH ('/both') and
|
||||||
|
Constants.WATCH_ONLY_PATH ('/watch-only') -->
|
||||||
|
<data android:scheme="wear" android:host="*" android:pathPrefix="/both"/>
|
||||||
|
<data android:scheme="wear" android:host="*" android:pathPrefix="/watch-only"/>;
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action
|
<action
|
||||||
|
|||||||
@@ -132,6 +132,42 @@
|
|||||||
<category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
|
<category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".ComplicationSimpleWatchFaceService"
|
||||||
|
android:enabled="true"
|
||||||
|
android:label="@string/complication_simple"
|
||||||
|
android:permission="android.permission.BIND_WALLPAPER">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.service.wallpaper"
|
||||||
|
android:resource="@xml/watch_face"/>
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.android.wearable.watchface.preview"
|
||||||
|
android:resource="@drawable/preview_complication_simple"/>
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.android.wearable.watchface.preview_circular"
|
||||||
|
android:resource="@drawable/preview_complication_simple"/>
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.android.wearable.watchface.wearableConfigurationAction"
|
||||||
|
android:value="com.example.android.wearable.watchface.CONFIG_COMPLICATION_SIMPLE"/>
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.service.wallpaper.WallpaperService"/>
|
||||||
|
|
||||||
|
<category android:name="com.google.android.wearable.watchface.category.WATCH_FACE"/>
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
<activity
|
||||||
|
android:name=".ComplicationSimpleConfigActivity"
|
||||||
|
android:label="@string/complication_simple">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.example.android.wearable.watchface.CONFIG_COMPLICATION_SIMPLE"/>
|
||||||
|
|
||||||
|
<category android:name="com.google.android.wearable.watchface.category.WEARABLE_CONFIGURATION"/>
|
||||||
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".InteractiveWatchFaceService"
|
android:name=".InteractiveWatchFaceService"
|
||||||
android:label="@string/interactive_name"
|
android:label="@string/interactive_name"
|
||||||
@@ -218,7 +254,8 @@
|
|||||||
</service>
|
</service>
|
||||||
<service android:name=".DigitalWatchFaceConfigListenerService" >
|
<service android:name=".DigitalWatchFaceConfigListenerService" >
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
|
<action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED" />
|
||||||
|
<data android:scheme="wear" android:host="*" android:pathPrefix="/"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
<service
|
<service
|
||||||
@@ -269,6 +306,35 @@
|
|||||||
android:name=".CalendarWatchFacePermissionActivity"
|
android:name=".CalendarWatchFacePermissionActivity"
|
||||||
android:label="@string/title_activity_calendar_watch_face_permission" >
|
android:label="@string/title_activity_calendar_watch_face_permission" >
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".provider.RandomNumberProviderService"
|
||||||
|
android:label="@string/complications_provider_random_number"
|
||||||
|
android:icon="@drawable/ic_launcher">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.support.wearable.complications.ACTION_COMPLICATION_UPDATE_REQUEST"/>
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.wearable.complications.SUPPORTED_TYPES"
|
||||||
|
android:value="RANGED_VALUE,SHORT_TEXT,LONG_TEXT"/>
|
||||||
|
<!--
|
||||||
|
When your complication data provider is active, UPDATE_PERIOD_SECONDS specifies how
|
||||||
|
often you want the system to check for updates to the data. In this case, the time is
|
||||||
|
specified to a relatively short 120 seconds, so we can observe the result of this code
|
||||||
|
lab. In everyday use, developers should consider intervals in the order of minutes.
|
||||||
|
Also, remember that this is only a guidance for the system. Android Wear may update less
|
||||||
|
frequently.
|
||||||
|
|
||||||
|
If your app needs to push updates instead of updating on a regular schedule, you should
|
||||||
|
set this value to 0 and use ProviderUpdateRequester.requestUpdate() to trigger an update
|
||||||
|
request when you need one.
|
||||||
|
-->
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.wearable.complications.UPDATE_PERIOD_SECONDS"
|
||||||
|
android:value="120"/>
|
||||||
|
</service>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
|
After Width: | Height: | Size: 460 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
@@ -0,0 +1,38 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2016 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.
|
||||||
|
-->
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@color/dark_grey"
|
||||||
|
android:paddingTop="4dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/header"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/complication_simple_config_name"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<android.support.wearable.view.WearableListView
|
||||||
|
android:id="@+id/wearable_list"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="match_parent">
|
||||||
|
</android.support.wearable.view.WearableListView>
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2016 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.
|
||||||
|
-->
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center">
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/name"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp"/>
|
||||||
|
</LinearLayout>
|
||||||
26
samples/browseable/WatchFace/Wearable/res/values/arrays.xml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?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.
|
||||||
|
-->
|
||||||
|
<resources>
|
||||||
|
<string-array name="complication_simple_names">
|
||||||
|
<item>Left dial</item>
|
||||||
|
<item>Right dial</item>
|
||||||
|
</string-array>
|
||||||
|
<array name="complication_simple_icons">
|
||||||
|
<item>@drawable/complications_left_dial</item>
|
||||||
|
<item>@drawable/complications_right_dial</item>
|
||||||
|
</array>
|
||||||
|
</resources>
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
<string name="opengl_name">Sample OpenGL</string>
|
<string name="opengl_name">Sample OpenGL</string>
|
||||||
<string name="interactive_name">Sample Interactive</string>
|
<string name="interactive_name">Sample Interactive</string>
|
||||||
<string name="analog_name">Sample Analog</string>
|
<string name="analog_name">Sample Analog</string>
|
||||||
|
<string name="complication_simple">Sample Complication Simple</string>
|
||||||
<string name="sweep_name">Sample Sweep</string>
|
<string name="sweep_name">Sample Sweep</string>
|
||||||
<string name="card_bounds_name">Sample Card Bounds</string>
|
<string name="card_bounds_name">Sample Card Bounds</string>
|
||||||
<string name="digital_name">Sample Digital</string>
|
<string name="digital_name">Sample Digital</string>
|
||||||
@@ -42,6 +43,9 @@
|
|||||||
<string name="title_activity_calendar_watch_face_permission">Calendar Permission Activity</string>
|
<string name="title_activity_calendar_watch_face_permission">Calendar Permission Activity</string>
|
||||||
<string name="calendar_permission_text">WatchFace requires Calendar access.</string>
|
<string name="calendar_permission_text">WatchFace requires Calendar access.</string>
|
||||||
|
|
||||||
|
<string name="complication_simple_config_name">Configuration</string>
|
||||||
|
<string name="complications_provider_random_number">Random Number</string>
|
||||||
|
|
||||||
<!-- TODO: this should be shared (needs covering all the samples with Gradle build model) -->
|
<!-- TODO: this should be shared (needs covering all the samples with Gradle build model) -->
|
||||||
<string name="color_black">Black</string>
|
<string name="color_black">Black</string>
|
||||||
<string name="color_blue">Blue</string>
|
<string name="color_blue">Blue</string>
|
||||||
|
|||||||
@@ -0,0 +1,191 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
package com.example.android.wearable.watchface;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.wearable.complications.ProviderChooserIntent;
|
||||||
|
import android.support.wearable.view.WearableListView;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The watch-side config activity for {@link ComplicationSimpleWatchFaceService}, which
|
||||||
|
* allows for setting complications on the left and right of watch face.
|
||||||
|
*/
|
||||||
|
public class ComplicationSimpleConfigActivity extends Activity implements
|
||||||
|
WearableListView.ClickListener {
|
||||||
|
|
||||||
|
private static final String TAG = "CompSimpleConfig";
|
||||||
|
|
||||||
|
private static final int PROVIDER_CHOOSER_REQUEST_CODE = 1;
|
||||||
|
|
||||||
|
private WearableListView mWearableConfigListView;
|
||||||
|
private ConfigurationAdapter mAdapter;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_complication_simple_config);
|
||||||
|
|
||||||
|
mAdapter = new ConfigurationAdapter(getApplicationContext(), getComplicationItems());
|
||||||
|
|
||||||
|
mWearableConfigListView = (WearableListView) findViewById(R.id.wearable_list);
|
||||||
|
mWearableConfigListView.setAdapter(mAdapter);
|
||||||
|
mWearableConfigListView.setClickListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
if (requestCode == PROVIDER_CHOOSER_REQUEST_CODE
|
||||||
|
&& resultCode == RESULT_OK) {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(WearableListView.ViewHolder viewHolder) {
|
||||||
|
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||||
|
Log.d(TAG, "onClick()");
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer tag = (Integer) viewHolder.itemView.getTag();
|
||||||
|
ComplicationItem complicationItem = mAdapter.getItem(tag);
|
||||||
|
|
||||||
|
startActivityForResult(ProviderChooserIntent.createProviderChooserIntent(
|
||||||
|
complicationItem.watchFace,
|
||||||
|
complicationItem.complicationId,
|
||||||
|
complicationItem.supportedTypes), PROVIDER_CHOOSER_REQUEST_CODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ComplicationItem> getComplicationItems() {
|
||||||
|
ComponentName watchFace = new ComponentName(
|
||||||
|
getApplicationContext(), ComplicationSimpleWatchFaceService.class);
|
||||||
|
|
||||||
|
String[] complicationNames =
|
||||||
|
getResources().getStringArray(R.array.complication_simple_names);
|
||||||
|
|
||||||
|
int[] complicationIds = ComplicationSimpleWatchFaceService.COMPLICATION_IDS;
|
||||||
|
|
||||||
|
TypedArray icons = getResources().obtainTypedArray(R.array.complication_simple_icons);
|
||||||
|
|
||||||
|
List<ComplicationItem> items = new ArrayList<>();
|
||||||
|
for (int i = 0; i < complicationIds.length; i++) {
|
||||||
|
items.add(new ComplicationItem(watchFace,
|
||||||
|
complicationIds[i],
|
||||||
|
ComplicationSimpleWatchFaceService.COMPLICATION_SUPPORTED_TYPES[i],
|
||||||
|
icons.getDrawable(i),
|
||||||
|
complicationNames[i]));
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTopEmptyRegionClick() {
|
||||||
|
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||||
|
Log.d(TAG, "onTopEmptyRegionClick()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Inner class representing items of the ConfigurationAdapter (WearableListView.Adapter) class.
|
||||||
|
*/
|
||||||
|
private final class ComplicationItem {
|
||||||
|
ComponentName watchFace;
|
||||||
|
int complicationId;
|
||||||
|
int[] supportedTypes;
|
||||||
|
Drawable icon;
|
||||||
|
String title;
|
||||||
|
|
||||||
|
public ComplicationItem(ComponentName watchFace, int complicationId, int[] supportedTypes,
|
||||||
|
Drawable icon, String title) {
|
||||||
|
this.watchFace = watchFace;
|
||||||
|
this.complicationId = complicationId;
|
||||||
|
this.supportedTypes = supportedTypes;
|
||||||
|
this.icon = icon;
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ConfigurationAdapter extends WearableListView.Adapter {
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
private final LayoutInflater mInflater;
|
||||||
|
private List<ComplicationItem> mItems;
|
||||||
|
|
||||||
|
|
||||||
|
public ConfigurationAdapter (Context context, List<ComplicationItem> items) {
|
||||||
|
mContext = context;
|
||||||
|
mInflater = LayoutInflater.from(mContext);
|
||||||
|
mItems = items;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provides a reference to the type of views you're using
|
||||||
|
public static class ItemViewHolder extends WearableListView.ViewHolder {
|
||||||
|
private ImageView iconImageView;
|
||||||
|
private TextView textView;
|
||||||
|
public ItemViewHolder(View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
iconImageView = (ImageView) itemView.findViewById(R.id.icon);
|
||||||
|
textView = (TextView) itemView.findViewById(R.id.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WearableListView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
|
||||||
|
// Inflate custom layout for list items.
|
||||||
|
return new ItemViewHolder(
|
||||||
|
mInflater.inflate(R.layout.activity_complication_simple_list_item, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(WearableListView.ViewHolder holder, int position) {
|
||||||
|
|
||||||
|
ItemViewHolder itemHolder = (ItemViewHolder) holder;
|
||||||
|
|
||||||
|
ImageView imageView = itemHolder.iconImageView;
|
||||||
|
imageView.setImageDrawable(mItems.get(position).icon);
|
||||||
|
|
||||||
|
TextView textView = itemHolder.textView;
|
||||||
|
textView.setText(mItems.get(position).title);
|
||||||
|
|
||||||
|
holder.itemView.setTag(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return mItems.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ComplicationItem getItem(int position) {
|
||||||
|
return mItems.get(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,744 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.android.wearable.watchface;
|
||||||
|
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.ColorMatrix;
|
||||||
|
import android.graphics.ColorMatrixColorFilter;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.Typeface;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.support.v7.graphics.Palette;
|
||||||
|
import android.support.wearable.complications.ComplicationData;
|
||||||
|
import android.support.wearable.complications.ComplicationText;
|
||||||
|
import android.support.wearable.watchface.CanvasWatchFaceService;
|
||||||
|
import android.support.wearable.watchface.WatchFaceService;
|
||||||
|
import android.support.wearable.watchface.WatchFaceStyle;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.SparseArray;
|
||||||
|
import android.view.SurfaceHolder;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demonstrates two simple complications in a watch face.
|
||||||
|
*/
|
||||||
|
public class ComplicationSimpleWatchFaceService extends CanvasWatchFaceService {
|
||||||
|
private static final String TAG = "SimpleComplicationWF";
|
||||||
|
|
||||||
|
// Unique IDs for each complication.
|
||||||
|
private static final int LEFT_DIAL_COMPLICATION = 0;
|
||||||
|
private static final int RIGHT_DIAL_COMPLICATION = 1;
|
||||||
|
|
||||||
|
// Left and right complication IDs as array for Complication API.
|
||||||
|
public static final int[] COMPLICATION_IDS = {LEFT_DIAL_COMPLICATION, RIGHT_DIAL_COMPLICATION};
|
||||||
|
|
||||||
|
// Left and right dial supported types.
|
||||||
|
public static final int[][] COMPLICATION_SUPPORTED_TYPES = {
|
||||||
|
{ComplicationData.TYPE_SHORT_TEXT},
|
||||||
|
{ComplicationData.TYPE_SHORT_TEXT}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Update rate in milliseconds for interactive mode. We update once a second to advance the
|
||||||
|
* second hand.
|
||||||
|
*/
|
||||||
|
private static final long INTERACTIVE_UPDATE_RATE_MS = TimeUnit.SECONDS.toMillis(1);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Engine onCreateEngine() {
|
||||||
|
return new Engine();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Engine extends CanvasWatchFaceService.Engine {
|
||||||
|
private static final int MSG_UPDATE_TIME = 0;
|
||||||
|
|
||||||
|
private static final float COMPLICATION_TEXT_SIZE = 38f;
|
||||||
|
private static final int COMPLICATION_TAP_BUFFER = 40;
|
||||||
|
|
||||||
|
private static final float HOUR_STROKE_WIDTH = 5f;
|
||||||
|
private static final float MINUTE_STROKE_WIDTH = 3f;
|
||||||
|
private static final float SECOND_TICK_STROKE_WIDTH = 2f;
|
||||||
|
|
||||||
|
private static final float CENTER_GAP_AND_CIRCLE_RADIUS = 4f;
|
||||||
|
|
||||||
|
private static final int SHADOW_RADIUS = 6;
|
||||||
|
|
||||||
|
private Calendar mCalendar;
|
||||||
|
private boolean mRegisteredTimeZoneReceiver = false;
|
||||||
|
private boolean mMuteMode;
|
||||||
|
|
||||||
|
private int mWidth;
|
||||||
|
private int mHeight;
|
||||||
|
private float mCenterX;
|
||||||
|
private float mCenterY;
|
||||||
|
|
||||||
|
private float mSecondHandLength;
|
||||||
|
private float mMinuteHandLength;
|
||||||
|
private float mHourHandLength;
|
||||||
|
|
||||||
|
// Colors for all hands (hour, minute, seconds, ticks) based on photo loaded.
|
||||||
|
private int mWatchHandColor;
|
||||||
|
private int mWatchHandHighlightColor;
|
||||||
|
private int mWatchHandShadowColor;
|
||||||
|
|
||||||
|
private Paint mHourPaint;
|
||||||
|
private Paint mMinutePaint;
|
||||||
|
private Paint mSecondPaint;
|
||||||
|
private Paint mTickAndCirclePaint;
|
||||||
|
|
||||||
|
private Paint mBackgroundPaint;
|
||||||
|
private Bitmap mBackgroundBitmap;
|
||||||
|
private Bitmap mGrayBackgroundBitmap;
|
||||||
|
|
||||||
|
// Variables for painting Complications
|
||||||
|
private Paint mComplicationPaint;
|
||||||
|
|
||||||
|
/* To properly place each complication, we need their x and y coordinates. While the width
|
||||||
|
* may change from moment to moment based on the time, the height will not change, so we
|
||||||
|
* store it as a local variable and only calculate it only when the surface changes
|
||||||
|
* (onSurfaceChanged()).
|
||||||
|
*/
|
||||||
|
private int mComplicationsY;
|
||||||
|
|
||||||
|
/* Maps active complication ids to the data for that complication. Note: Data will only be
|
||||||
|
* present if the user has chosen a provider via the settings activity for the watch face.
|
||||||
|
*/
|
||||||
|
private SparseArray<ComplicationData> mActiveComplicationDataSparseArray;
|
||||||
|
|
||||||
|
private boolean mAmbient;
|
||||||
|
private boolean mLowBitAmbient;
|
||||||
|
private boolean mBurnInProtection;
|
||||||
|
|
||||||
|
private Rect mPeekCardBounds = new Rect();
|
||||||
|
|
||||||
|
private final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
mCalendar.setTimeZone(TimeZone.getDefault());
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handler to update the time once a second in interactive mode.
|
||||||
|
private final Handler mUpdateTimeHandler = new Handler() {
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message message) {
|
||||||
|
|
||||||
|
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||||
|
Log.d(TAG, "updating time");
|
||||||
|
}
|
||||||
|
invalidate();
|
||||||
|
if (shouldTimerBeRunning()) {
|
||||||
|
long timeMs = System.currentTimeMillis();
|
||||||
|
long delayMs = INTERACTIVE_UPDATE_RATE_MS
|
||||||
|
- (timeMs % INTERACTIVE_UPDATE_RATE_MS);
|
||||||
|
mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(SurfaceHolder holder) {
|
||||||
|
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||||
|
Log.d(TAG, "onCreate");
|
||||||
|
}
|
||||||
|
super.onCreate(holder);
|
||||||
|
|
||||||
|
mCalendar = Calendar.getInstance();
|
||||||
|
|
||||||
|
setWatchFaceStyle(new WatchFaceStyle.Builder(ComplicationSimpleWatchFaceService.this)
|
||||||
|
.setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
|
||||||
|
.setAcceptsTapEvents(true)
|
||||||
|
.setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
|
||||||
|
.setShowSystemUiTime(false)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
initializeBackground();
|
||||||
|
initializeComplication();
|
||||||
|
initializeWatchFace();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeBackground() {
|
||||||
|
mBackgroundPaint = new Paint();
|
||||||
|
mBackgroundPaint.setColor(Color.BLACK);
|
||||||
|
mBackgroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeComplication() {
|
||||||
|
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||||
|
Log.d(TAG, "initializeComplications()");
|
||||||
|
}
|
||||||
|
mActiveComplicationDataSparseArray = new SparseArray<>(COMPLICATION_IDS.length);
|
||||||
|
|
||||||
|
mComplicationPaint = new Paint();
|
||||||
|
mComplicationPaint.setColor(Color.WHITE);
|
||||||
|
mComplicationPaint.setTextSize(COMPLICATION_TEXT_SIZE);
|
||||||
|
mComplicationPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
|
||||||
|
mComplicationPaint.setAntiAlias(true);
|
||||||
|
|
||||||
|
setActiveComplications(COMPLICATION_IDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeWatchFace() {
|
||||||
|
/* Set defaults for colors */
|
||||||
|
mWatchHandColor = Color.WHITE;
|
||||||
|
mWatchHandHighlightColor = Color.RED;
|
||||||
|
mWatchHandShadowColor = Color.BLACK;
|
||||||
|
|
||||||
|
mHourPaint = new Paint();
|
||||||
|
mHourPaint.setColor(mWatchHandColor);
|
||||||
|
mHourPaint.setStrokeWidth(HOUR_STROKE_WIDTH);
|
||||||
|
mHourPaint.setAntiAlias(true);
|
||||||
|
mHourPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||||
|
mHourPaint.setShadowLayer(SHADOW_RADIUS, 0, 0, mWatchHandShadowColor);
|
||||||
|
|
||||||
|
mMinutePaint = new Paint();
|
||||||
|
mMinutePaint.setColor(mWatchHandColor);
|
||||||
|
mMinutePaint.setStrokeWidth(MINUTE_STROKE_WIDTH);
|
||||||
|
mMinutePaint.setAntiAlias(true);
|
||||||
|
mMinutePaint.setStrokeCap(Paint.Cap.ROUND);
|
||||||
|
mMinutePaint.setShadowLayer(SHADOW_RADIUS, 0, 0, mWatchHandShadowColor);
|
||||||
|
|
||||||
|
mSecondPaint = new Paint();
|
||||||
|
mSecondPaint.setColor(mWatchHandHighlightColor);
|
||||||
|
mSecondPaint.setStrokeWidth(SECOND_TICK_STROKE_WIDTH);
|
||||||
|
mSecondPaint.setAntiAlias(true);
|
||||||
|
mSecondPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||||
|
mSecondPaint.setShadowLayer(SHADOW_RADIUS, 0, 0, mWatchHandShadowColor);
|
||||||
|
|
||||||
|
mTickAndCirclePaint = new Paint();
|
||||||
|
mTickAndCirclePaint.setColor(mWatchHandColor);
|
||||||
|
mTickAndCirclePaint.setStrokeWidth(SECOND_TICK_STROKE_WIDTH);
|
||||||
|
mTickAndCirclePaint.setAntiAlias(true);
|
||||||
|
mTickAndCirclePaint.setStyle(Paint.Style.STROKE);
|
||||||
|
mTickAndCirclePaint.setShadowLayer(SHADOW_RADIUS, 0, 0, mWatchHandShadowColor);
|
||||||
|
|
||||||
|
// Asynchronous call extract colors from background image to improve watch face style.
|
||||||
|
Palette.from(mBackgroundBitmap).generate(
|
||||||
|
new Palette.PaletteAsyncListener() {
|
||||||
|
public void onGenerated(Palette palette) {
|
||||||
|
/*
|
||||||
|
* Sometimes, palette is unable to generate a color palette
|
||||||
|
* so we need to check that we have one.
|
||||||
|
*/
|
||||||
|
if (palette != null) {
|
||||||
|
Log.d("onGenerated", palette.toString());
|
||||||
|
mWatchHandColor = palette.getVibrantColor(Color.WHITE);
|
||||||
|
mWatchHandShadowColor = palette.getDarkMutedColor(Color.BLACK);
|
||||||
|
updateWatchHandStyle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPropertiesChanged(Bundle properties) {
|
||||||
|
super.onPropertiesChanged(properties);
|
||||||
|
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||||
|
Log.d(TAG, "onPropertiesChanged: low-bit ambient = " + mLowBitAmbient);
|
||||||
|
}
|
||||||
|
|
||||||
|
mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false);
|
||||||
|
mBurnInProtection = properties.getBoolean(PROPERTY_BURN_IN_PROTECTION, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Called when there is updated data for a complication id.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onComplicationDataUpdate(
|
||||||
|
int complicationId, ComplicationData complicationData) {
|
||||||
|
Log.d(TAG, "onComplicationDataUpdate() id: " + complicationId);
|
||||||
|
|
||||||
|
// Adds/updates active complication data in the array.
|
||||||
|
mActiveComplicationDataSparseArray.put(complicationId, complicationData);
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTapCommand(int tapType, int x, int y, long eventTime) {
|
||||||
|
Log.d(TAG, "OnTapCommand()");
|
||||||
|
switch (tapType) {
|
||||||
|
case TAP_TYPE_TAP:
|
||||||
|
int tappedComplicationId = getTappedComplicationId(x, y);
|
||||||
|
if (tappedComplicationId != -1) {
|
||||||
|
onComplicationTap(tappedComplicationId);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Determines if tap inside a complication area or returns -1.
|
||||||
|
*/
|
||||||
|
private int getTappedComplicationId(int x, int y) {
|
||||||
|
ComplicationData complicationData;
|
||||||
|
long currentTimeMillis = System.currentTimeMillis();
|
||||||
|
|
||||||
|
for (int i = 0; i < COMPLICATION_IDS.length; i++) {
|
||||||
|
complicationData = mActiveComplicationDataSparseArray.get(COMPLICATION_IDS[i]);
|
||||||
|
|
||||||
|
if ((complicationData != null)
|
||||||
|
&& (complicationData.isActive(currentTimeMillis))
|
||||||
|
&& (complicationData.getType() != ComplicationData.TYPE_NOT_CONFIGURED)
|
||||||
|
&& (complicationData.getType() != ComplicationData.TYPE_EMPTY)) {
|
||||||
|
|
||||||
|
Rect complicationBoundingRect = new Rect(0, 0, 0, 0);
|
||||||
|
|
||||||
|
switch (COMPLICATION_IDS[i]) {
|
||||||
|
case LEFT_DIAL_COMPLICATION:
|
||||||
|
complicationBoundingRect.set(
|
||||||
|
0, // left
|
||||||
|
mComplicationsY - COMPLICATION_TAP_BUFFER, // top
|
||||||
|
(mWidth / 2), // right
|
||||||
|
((int) COMPLICATION_TEXT_SIZE // bottom
|
||||||
|
+ mComplicationsY
|
||||||
|
+ COMPLICATION_TAP_BUFFER));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RIGHT_DIAL_COMPLICATION:
|
||||||
|
complicationBoundingRect.set(
|
||||||
|
(mWidth / 2), // left
|
||||||
|
mComplicationsY - COMPLICATION_TAP_BUFFER, // top
|
||||||
|
mWidth, // right
|
||||||
|
((int) COMPLICATION_TEXT_SIZE // bottom
|
||||||
|
+ mComplicationsY
|
||||||
|
+ COMPLICATION_TAP_BUFFER));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (complicationBoundingRect.width() > 0) {
|
||||||
|
if (complicationBoundingRect.contains(x, y)) {
|
||||||
|
return COMPLICATION_IDS[i];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Not a recognized complication id.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fires PendingIntent associated with complication (if it has one).
|
||||||
|
*/
|
||||||
|
private void onComplicationTap(int complicationId) {
|
||||||
|
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||||
|
Log.d(TAG, "onComplicationTap()");
|
||||||
|
}
|
||||||
|
ComplicationData complicationData =
|
||||||
|
mActiveComplicationDataSparseArray.get(complicationId);
|
||||||
|
|
||||||
|
if ((complicationData != null) && (complicationData.getTapAction() != null)) {
|
||||||
|
try {
|
||||||
|
complicationData.getTapAction().send();
|
||||||
|
} catch (PendingIntent.CanceledException e) {
|
||||||
|
Log.e(TAG, "On complication tap action error " + e);
|
||||||
|
}
|
||||||
|
invalidate();
|
||||||
|
} else {
|
||||||
|
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||||
|
Log.d(TAG, "No PendingIntent for complication " + complicationId + ".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTimeTick() {
|
||||||
|
super.onTimeTick();
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAmbientModeChanged(boolean inAmbientMode) {
|
||||||
|
super.onAmbientModeChanged(inAmbientMode);
|
||||||
|
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||||
|
Log.d(TAG, "onAmbientModeChanged: " + inAmbientMode);
|
||||||
|
}
|
||||||
|
mAmbient = inAmbientMode;
|
||||||
|
|
||||||
|
updateWatchHandStyle();
|
||||||
|
|
||||||
|
// Updates complication style
|
||||||
|
mComplicationPaint.setAntiAlias(!inAmbientMode);
|
||||||
|
|
||||||
|
// Check and trigger whether or not timer should be running (only in active mode).
|
||||||
|
updateTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateWatchHandStyle(){
|
||||||
|
if (mAmbient){
|
||||||
|
mHourPaint.setColor(Color.WHITE);
|
||||||
|
mMinutePaint.setColor(Color.WHITE);
|
||||||
|
mSecondPaint.setColor(Color.WHITE);
|
||||||
|
mTickAndCirclePaint.setColor(Color.WHITE);
|
||||||
|
|
||||||
|
mHourPaint.setAntiAlias(false);
|
||||||
|
mMinutePaint.setAntiAlias(false);
|
||||||
|
mSecondPaint.setAntiAlias(false);
|
||||||
|
mTickAndCirclePaint.setAntiAlias(false);
|
||||||
|
|
||||||
|
mHourPaint.clearShadowLayer();
|
||||||
|
mMinutePaint.clearShadowLayer();
|
||||||
|
mSecondPaint.clearShadowLayer();
|
||||||
|
mTickAndCirclePaint.clearShadowLayer();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
mHourPaint.setColor(mWatchHandColor);
|
||||||
|
mMinutePaint.setColor(mWatchHandColor);
|
||||||
|
mSecondPaint.setColor(mWatchHandHighlightColor);
|
||||||
|
mTickAndCirclePaint.setColor(mWatchHandColor);
|
||||||
|
|
||||||
|
mHourPaint.setAntiAlias(true);
|
||||||
|
mMinutePaint.setAntiAlias(true);
|
||||||
|
mSecondPaint.setAntiAlias(true);
|
||||||
|
mTickAndCirclePaint.setAntiAlias(true);
|
||||||
|
|
||||||
|
mHourPaint.setShadowLayer(SHADOW_RADIUS, 0, 0, mWatchHandShadowColor);
|
||||||
|
mMinutePaint.setShadowLayer(SHADOW_RADIUS, 0, 0, mWatchHandShadowColor);
|
||||||
|
mSecondPaint.setShadowLayer(SHADOW_RADIUS, 0, 0, mWatchHandShadowColor);
|
||||||
|
mTickAndCirclePaint.setShadowLayer(SHADOW_RADIUS, 0, 0, mWatchHandShadowColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInterruptionFilterChanged(int interruptionFilter) {
|
||||||
|
super.onInterruptionFilterChanged(interruptionFilter);
|
||||||
|
boolean inMuteMode = (interruptionFilter == WatchFaceService.INTERRUPTION_FILTER_NONE);
|
||||||
|
|
||||||
|
/* Dim display in mute mode. */
|
||||||
|
if (mMuteMode != inMuteMode) {
|
||||||
|
mMuteMode = inMuteMode;
|
||||||
|
mHourPaint.setAlpha(inMuteMode ? 100 : 255);
|
||||||
|
mMinutePaint.setAlpha(inMuteMode ? 100 : 255);
|
||||||
|
mSecondPaint.setAlpha(inMuteMode ? 80 : 255);
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||||
|
super.onSurfaceChanged(holder, format, width, height);
|
||||||
|
|
||||||
|
// Used for complications
|
||||||
|
mWidth = width;
|
||||||
|
mHeight = height;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Find the coordinates of the center point on the screen, and ignore the window
|
||||||
|
* insets, so that, on round watches with a "chin", the watch face is centered on the
|
||||||
|
* entire screen, not just the usable portion.
|
||||||
|
*/
|
||||||
|
mCenterX = mWidth / 2f;
|
||||||
|
mCenterY = mHeight / 2f;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Since the height of the complications text does not change, we only have to
|
||||||
|
* recalculate when the surface changes.
|
||||||
|
*/
|
||||||
|
mComplicationsY = (int) ((mHeight / 2) + (mComplicationPaint.getTextSize() / 2));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Calculate lengths of different hands based on watch screen size.
|
||||||
|
*/
|
||||||
|
mSecondHandLength = (float) (mCenterX * 0.875);
|
||||||
|
mMinuteHandLength = (float) (mCenterX * 0.75);
|
||||||
|
mHourHandLength = (float) (mCenterX * 0.5);
|
||||||
|
|
||||||
|
|
||||||
|
/* Scale loaded background image (more efficient) if surface dimensions change. */
|
||||||
|
float scale = ((float) width) / (float) mBackgroundBitmap.getWidth();
|
||||||
|
|
||||||
|
mBackgroundBitmap = Bitmap.createScaledBitmap(mBackgroundBitmap,
|
||||||
|
(int) (mBackgroundBitmap.getWidth() * scale),
|
||||||
|
(int) (mBackgroundBitmap.getHeight() * scale), true);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a gray version of the image only if it will look nice on the device in
|
||||||
|
* ambient mode. That means we don't want devices that support burn-in
|
||||||
|
* protection (slight movements in pixels, not great for images going all the way to
|
||||||
|
* edges) and low ambient mode (degrades image quality).
|
||||||
|
*
|
||||||
|
* Also, if your watch face will know about all images ahead of time (users aren't
|
||||||
|
* selecting their own photos for the watch face), it will be more
|
||||||
|
* efficient to create a black/white version (png, etc.) and load that when you need it.
|
||||||
|
*/
|
||||||
|
if (!mBurnInProtection && !mLowBitAmbient) {
|
||||||
|
initGrayBackgroundBitmap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initGrayBackgroundBitmap() {
|
||||||
|
mGrayBackgroundBitmap = Bitmap.createBitmap(
|
||||||
|
mBackgroundBitmap.getWidth(),
|
||||||
|
mBackgroundBitmap.getHeight(),
|
||||||
|
Bitmap.Config.ARGB_8888);
|
||||||
|
Canvas canvas = new Canvas(mGrayBackgroundBitmap);
|
||||||
|
Paint grayPaint = new Paint();
|
||||||
|
ColorMatrix colorMatrix = new ColorMatrix();
|
||||||
|
colorMatrix.setSaturation(0);
|
||||||
|
ColorMatrixColorFilter filter = new ColorMatrixColorFilter(colorMatrix);
|
||||||
|
grayPaint.setColorFilter(filter);
|
||||||
|
canvas.drawBitmap(mBackgroundBitmap, 0, 0, grayPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDraw(Canvas canvas, Rect bounds) {
|
||||||
|
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||||
|
Log.d(TAG, "onDraw");
|
||||||
|
}
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
mCalendar.setTimeInMillis(now);
|
||||||
|
|
||||||
|
drawBackground(canvas);
|
||||||
|
drawComplications(canvas, now);
|
||||||
|
drawWatchFace(canvas);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawBackground(Canvas canvas) {
|
||||||
|
if (mAmbient && (mLowBitAmbient || mBurnInProtection)) {
|
||||||
|
canvas.drawColor(Color.BLACK);
|
||||||
|
} else if (mAmbient) {
|
||||||
|
canvas.drawBitmap(mGrayBackgroundBitmap, 0, 0, mBackgroundPaint);
|
||||||
|
} else {
|
||||||
|
canvas.drawBitmap(mBackgroundBitmap, 0, 0, mBackgroundPaint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawComplications(Canvas canvas, long currentTimeMillis) {
|
||||||
|
ComplicationData complicationData;
|
||||||
|
|
||||||
|
for (int i = 0; i < COMPLICATION_IDS.length; i++) {
|
||||||
|
|
||||||
|
complicationData = mActiveComplicationDataSparseArray.get(COMPLICATION_IDS[i]);
|
||||||
|
|
||||||
|
if ((complicationData != null)
|
||||||
|
&& (complicationData.isActive(currentTimeMillis))
|
||||||
|
&& (complicationData.getType() == ComplicationData.TYPE_SHORT_TEXT)) {
|
||||||
|
|
||||||
|
ComplicationText mainText = complicationData.getShortText();
|
||||||
|
ComplicationText subText = complicationData.getShortTitle();
|
||||||
|
|
||||||
|
CharSequence complicationMessage =
|
||||||
|
mainText.getText(getApplicationContext(), currentTimeMillis);
|
||||||
|
|
||||||
|
/* In most cases you would want the subText (Title) under the mainText (Text),
|
||||||
|
* but to keep it simple for the code lab, we are concatenating them all on one
|
||||||
|
* line.
|
||||||
|
*/
|
||||||
|
if (subText != null) {
|
||||||
|
complicationMessage = TextUtils.concat(
|
||||||
|
complicationMessage,
|
||||||
|
" ",
|
||||||
|
subText.getText(getApplicationContext(), currentTimeMillis));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Log.d(TAG, "Comp id: " + COMPLICATION_IDS[i] + "\t" + complicationMessage);
|
||||||
|
double textWidth =
|
||||||
|
mComplicationPaint.measureText(
|
||||||
|
complicationMessage,
|
||||||
|
0,
|
||||||
|
complicationMessage.length());
|
||||||
|
|
||||||
|
int complicationsX;
|
||||||
|
|
||||||
|
if (COMPLICATION_IDS[i] == LEFT_DIAL_COMPLICATION) {
|
||||||
|
complicationsX = (int) ((mWidth / 2) - textWidth) / 2;
|
||||||
|
} else {
|
||||||
|
// RIGHT_DIAL_COMPLICATION calculations
|
||||||
|
int offset = (int) ((mWidth / 2) - textWidth) / 2;
|
||||||
|
complicationsX = (mWidth / 2) + offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.drawText(
|
||||||
|
complicationMessage,
|
||||||
|
0,
|
||||||
|
complicationMessage.length(),
|
||||||
|
complicationsX,
|
||||||
|
mComplicationsY,
|
||||||
|
mComplicationPaint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawWatchFace(Canvas canvas) {
|
||||||
|
/*
|
||||||
|
* Draw ticks. Usually you will want to bake this directly into the photo, but in
|
||||||
|
* cases where you want to allow users to select their own photos, this dynamically
|
||||||
|
* creates them on top of the photo.
|
||||||
|
*/
|
||||||
|
float innerTickRadius = mCenterX - 10;
|
||||||
|
float outerTickRadius = mCenterX;
|
||||||
|
for (int tickIndex = 0; tickIndex < 12; tickIndex++) {
|
||||||
|
float tickRot = (float) (tickIndex * Math.PI * 2 / 12);
|
||||||
|
float innerX = (float) Math.sin(tickRot) * innerTickRadius;
|
||||||
|
float innerY = (float) -Math.cos(tickRot) * innerTickRadius;
|
||||||
|
float outerX = (float) Math.sin(tickRot) * outerTickRadius;
|
||||||
|
float outerY = (float) -Math.cos(tickRot) * outerTickRadius;
|
||||||
|
canvas.drawLine(mCenterX + innerX, mCenterY + innerY,
|
||||||
|
mCenterX + outerX, mCenterY + outerY, mTickAndCirclePaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These calculations reflect the rotation in degrees per unit of time, e.g.,
|
||||||
|
* 360 / 60 = 6 and 360 / 12 = 30.
|
||||||
|
*/
|
||||||
|
final float seconds =
|
||||||
|
(mCalendar.get(Calendar.SECOND) + mCalendar.get(Calendar.MILLISECOND) / 1000f);
|
||||||
|
final float secondsRotation = seconds * 6f;
|
||||||
|
|
||||||
|
final float minutesRotation = mCalendar.get(Calendar.MINUTE) * 6f;
|
||||||
|
|
||||||
|
final float hourHandOffset = mCalendar.get(Calendar.MINUTE) / 2f;
|
||||||
|
final float hoursRotation = (mCalendar.get(Calendar.HOUR) * 30) + hourHandOffset;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Save the canvas state before we can begin to rotate it.
|
||||||
|
*/
|
||||||
|
canvas.save();
|
||||||
|
|
||||||
|
canvas.rotate(hoursRotation, mCenterX, mCenterY);
|
||||||
|
canvas.drawLine(
|
||||||
|
mCenterX,
|
||||||
|
mCenterY - CENTER_GAP_AND_CIRCLE_RADIUS,
|
||||||
|
mCenterX,
|
||||||
|
mCenterY - mHourHandLength,
|
||||||
|
mHourPaint);
|
||||||
|
|
||||||
|
canvas.rotate(minutesRotation - hoursRotation, mCenterX, mCenterY);
|
||||||
|
canvas.drawLine(
|
||||||
|
mCenterX,
|
||||||
|
mCenterY - CENTER_GAP_AND_CIRCLE_RADIUS,
|
||||||
|
mCenterX,
|
||||||
|
mCenterY - mMinuteHandLength,
|
||||||
|
mMinutePaint);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ensure the "seconds" hand is drawn only when we are in interactive mode.
|
||||||
|
* Otherwise, we only update the watch face once a minute.
|
||||||
|
*/
|
||||||
|
if (!mAmbient) {
|
||||||
|
canvas.rotate(secondsRotation - minutesRotation, mCenterX, mCenterY);
|
||||||
|
canvas.drawLine(
|
||||||
|
mCenterX,
|
||||||
|
mCenterY - CENTER_GAP_AND_CIRCLE_RADIUS,
|
||||||
|
mCenterX,
|
||||||
|
mCenterY - mSecondHandLength,
|
||||||
|
mSecondPaint);
|
||||||
|
|
||||||
|
}
|
||||||
|
canvas.drawCircle(
|
||||||
|
mCenterX,
|
||||||
|
mCenterY,
|
||||||
|
CENTER_GAP_AND_CIRCLE_RADIUS,
|
||||||
|
mTickAndCirclePaint);
|
||||||
|
|
||||||
|
/* Restore the canvas' original orientation. */
|
||||||
|
canvas.restore();
|
||||||
|
|
||||||
|
/* Draw rectangle behind peek card in ambient mode to improve readability. */
|
||||||
|
if (mAmbient) {
|
||||||
|
canvas.drawRect(mPeekCardBounds, mBackgroundPaint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onVisibilityChanged(boolean visible) {
|
||||||
|
super.onVisibilityChanged(visible);
|
||||||
|
|
||||||
|
if (visible) {
|
||||||
|
registerReceiver();
|
||||||
|
// Update time zone in case it changed while we weren't visible.
|
||||||
|
mCalendar.setTimeZone(TimeZone.getDefault());
|
||||||
|
invalidate();
|
||||||
|
} else {
|
||||||
|
unregisterReceiver();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check and trigger whether or not timer should be running (only in active mode). */
|
||||||
|
updateTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPeekCardPositionUpdate(Rect rect) {
|
||||||
|
super.onPeekCardPositionUpdate(rect);
|
||||||
|
mPeekCardBounds.set(rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerReceiver() {
|
||||||
|
if (mRegisteredTimeZoneReceiver) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mRegisteredTimeZoneReceiver = true;
|
||||||
|
IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
|
||||||
|
ComplicationSimpleWatchFaceService.this.registerReceiver(mTimeZoneReceiver, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unregisterReceiver() {
|
||||||
|
if (!mRegisteredTimeZoneReceiver) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mRegisteredTimeZoneReceiver = false;
|
||||||
|
ComplicationSimpleWatchFaceService.this.unregisterReceiver(mTimeZoneReceiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts/stops the {@link #mUpdateTimeHandler} timer based on the state of the watch face.
|
||||||
|
*/
|
||||||
|
private void updateTimer() {
|
||||||
|
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||||
|
Log.d(TAG, "updateTimer");
|
||||||
|
}
|
||||||
|
mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
|
||||||
|
if (shouldTimerBeRunning()) {
|
||||||
|
mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the {@link #mUpdateTimeHandler} timer should be running. The timer
|
||||||
|
* should only run in active mode.
|
||||||
|
*/
|
||||||
|
private boolean shouldTimerBeRunning() {
|
||||||
|
return isVisible() && !mAmbient;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,6 +40,11 @@ public class DigitalWatchFaceConfigListenerService extends WearableListenerServi
|
|||||||
|
|
||||||
@Override // WearableListenerService
|
@Override // WearableListenerService
|
||||||
public void onMessageReceived(MessageEvent messageEvent) {
|
public void onMessageReceived(MessageEvent messageEvent) {
|
||||||
|
|
||||||
|
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||||
|
Log.d(TAG, "onMessageReceived: " + messageEvent);
|
||||||
|
}
|
||||||
|
|
||||||
if (!messageEvent.getPath().equals(DigitalWatchFaceUtil.PATH_WITH_FEATURE)) {
|
if (!messageEvent.getPath().equals(DigitalWatchFaceUtil.PATH_WITH_FEATURE)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,10 @@ import java.util.concurrent.TimeUnit;
|
|||||||
*
|
*
|
||||||
* Authentication IS a requirement to request distance from Google Fit on Wear. Otherwise, distance
|
* Authentication IS a requirement to request distance from Google Fit on Wear. Otherwise, distance
|
||||||
* will always come back as zero (or stay at whatever the distance was prior to you
|
* will always come back as zero (or stay at whatever the distance was prior to you
|
||||||
* de-authorizing watchface).
|
* de-authorizing watchface). To authenticate and communicate with Google Fit, you must create a
|
||||||
|
* project in the Google Developers Console, activate the Fitness API, create an OAuth 2.0
|
||||||
|
* client ID, and register the public certificate from your app's signed APK. More details can be
|
||||||
|
* found here: https://developers.google.com/fit/android/get-started#step_3_enable_the_fitness_api
|
||||||
*
|
*
|
||||||
* In ambient mode, the seconds are replaced with an AM/PM indicator.
|
* In ambient mode, the seconds are replaced with an AM/PM indicator.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package com.example.android.wearable.watchface.provider;
|
||||||
|
|
||||||
|
import android.support.wearable.complications.ComplicationData;
|
||||||
|
import android.support.wearable.complications.ComplicationManager;
|
||||||
|
import android.support.wearable.complications.ComplicationProviderService;
|
||||||
|
import android.support.wearable.complications.ComplicationText;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example Watch Face Complication data provider provides a random number on every update.
|
||||||
|
*/
|
||||||
|
public class RandomNumberProviderService extends ComplicationProviderService {
|
||||||
|
|
||||||
|
private static final String TAG = "RandomNumberProvider";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Called when a complication has been activated. The method is for any one-time
|
||||||
|
* (per complication) set-up.
|
||||||
|
*
|
||||||
|
* You can continue sending data for the active complicationId until onComplicationDeactivated()
|
||||||
|
* is called.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onComplicationActivated(
|
||||||
|
int complicationId, int dataType, ComplicationManager complicationManager) {
|
||||||
|
Log.d(TAG, "onComplicationActivated(): " + complicationId);
|
||||||
|
super.onComplicationActivated(complicationId, dataType, complicationManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Called when the complication needs updated data from your provider. There are four scenarios
|
||||||
|
* when this will happen:
|
||||||
|
*
|
||||||
|
* 1. An active watch face complication is changed to use this provider
|
||||||
|
* 2. A complication using this provider becomes active
|
||||||
|
* 3. The period of time you specified in the manifest has elapsed (UPDATE_PERIOD_SECONDS)
|
||||||
|
* 4. You triggered an update from your own class via the
|
||||||
|
* ProviderUpdateRequester.requestUpdate() method.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onComplicationUpdate(
|
||||||
|
int complicationId, int dataType, ComplicationManager complicationManager) {
|
||||||
|
Log.d(TAG, "onComplicationUpdate()");
|
||||||
|
|
||||||
|
|
||||||
|
// Retrieve your data, in this case, we simply create a random number to display.
|
||||||
|
int randomNumber = (int) Math.floor(Math.random() * 10);
|
||||||
|
|
||||||
|
String randomNumberText =
|
||||||
|
String.format(Locale.getDefault(), "%d!", randomNumber);
|
||||||
|
|
||||||
|
ComplicationData complicationData = null;
|
||||||
|
|
||||||
|
switch (dataType) {
|
||||||
|
case ComplicationData.TYPE_RANGED_VALUE:
|
||||||
|
complicationData = new ComplicationData.Builder(ComplicationData.TYPE_RANGED_VALUE)
|
||||||
|
.setValue(randomNumber)
|
||||||
|
.setMinValue(0)
|
||||||
|
.setMaxValue(10)
|
||||||
|
.setShortText(ComplicationText.plainText(randomNumberText))
|
||||||
|
.build();
|
||||||
|
break;
|
||||||
|
case ComplicationData.TYPE_SHORT_TEXT:
|
||||||
|
complicationData = new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
|
||||||
|
.setShortText(ComplicationText.plainText(randomNumberText))
|
||||||
|
.build();
|
||||||
|
break;
|
||||||
|
case ComplicationData.TYPE_LONG_TEXT:
|
||||||
|
complicationData = new ComplicationData.Builder(ComplicationData.TYPE_LONG_TEXT)
|
||||||
|
.setLongText(
|
||||||
|
ComplicationText.plainText("Random Number: " + randomNumberText))
|
||||||
|
.build();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (Log.isLoggable(TAG, Log.WARN)) {
|
||||||
|
Log.w(TAG, "Unexpected complication type " + dataType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (complicationData != null) {
|
||||||
|
complicationManager.updateComplicationData(complicationId, complicationData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Called when the complication has been deactivated. If you are updating the complication
|
||||||
|
* manager outside of this class with updates, you will want to update your class to stop.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onComplicationDeactivated(int complicationId) {
|
||||||
|
Log.d(TAG, "onComplicationDeactivated(): " + complicationId);
|
||||||
|
super.onComplicationDeactivated(complicationId);
|
||||||
|
}
|
||||||
|
}
|
||||||
29
samples/browseable/WearDrawers/AndroidManifest.xml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest package="com.example.android.wearable.wear.weardrawers"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<uses-feature android:name="android.hardware.type.watch"/>
|
||||||
|
<!-- Required for Always-on. -->
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@android:style/Theme.DeviceDefault">
|
||||||
|
|
||||||
|
<!--If you want your app to run on pre-22, then set required to false -->
|
||||||
|
<uses-library android:name="com.google.android.wearable" android:required="false" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:label="@string/app_name">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
10
samples/browseable/WearDrawers/_index.jd
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
page.tags="WearDrawers"
|
||||||
|
sample.group=Wearable
|
||||||
|
@jd:body
|
||||||
|
|
||||||
|
<p>
|
||||||
|
|
||||||
|
A simple sample that demonstrates Navigation and Action Drawers, part of Material Design for Wear.
|
||||||
|
|
||||||
|
</p>
|
||||||
BIN
samples/browseable/WearDrawers/res/drawable-hdpi/earth.png
Normal file
|
After Width: | Height: | Size: 151 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 388 B |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
BIN
samples/browseable/WearDrawers/res/drawable-hdpi/jupiter.png
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
samples/browseable/WearDrawers/res/drawable-hdpi/mars.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
samples/browseable/WearDrawers/res/drawable-hdpi/mercury.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
samples/browseable/WearDrawers/res/drawable-hdpi/neptune.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
samples/browseable/WearDrawers/res/drawable-hdpi/saturn.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
samples/browseable/WearDrawers/res/drawable-hdpi/uranus.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
samples/browseable/WearDrawers/res/drawable-hdpi/venus.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 254 B |
|
After Width: | Height: | Size: 487 B |
|
After Width: | Height: | Size: 717 B |
|
After Width: | Height: | Size: 940 B |
@@ -0,0 +1,20 @@
|
|||||||
|
<!--
|
||||||
|
Copyright 2016 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.
|
||||||
|
-->
|
||||||
|
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/image"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"/>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2016 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.
|
||||||
|
-->
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:id="@+id/menu_planet_name"
|
||||||
|
android:icon="@drawable/ic_info_outline_black_18dp"
|
||||||
|
android:title="Planet Name"/>
|
||||||
|
<item android:id="@+id/menu_number_of_moons"
|
||||||
|
android:icon="@drawable/ic_info_outline_black_18dp"
|
||||||
|
android:title="Number of Moons" />
|
||||||
|
<item android:id="@+id/menu_volume"
|
||||||
|
android:icon="@drawable/ic_info_outline_black_18dp"
|
||||||
|
android:title="Volume" />
|
||||||
|
<item android:id="@+id/menu_surface_area"
|
||||||
|
android:icon="@drawable/ic_info_outline_black_18dp"
|
||||||
|
android:title="Surface Area" />
|
||||||
|
</menu>
|
||||||
BIN
samples/browseable/WearDrawers/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
samples/browseable/WearDrawers/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
samples/browseable/WearDrawers/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
samples/browseable/WearDrawers/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
114
samples/browseable/WearDrawers/res/values/strings.xml
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
<!--
|
||||||
|
Copyright 2016 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.
|
||||||
|
-->
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">Wearable Drawer Layout</string>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Reference name for each planet's information array.
|
||||||
|
-->
|
||||||
|
<string-array name="planets_array_names">
|
||||||
|
<item>mercury</item>
|
||||||
|
<item>venus</item>
|
||||||
|
<item>earth</item>
|
||||||
|
<item>mars</item>
|
||||||
|
<item>jupiter</item>
|
||||||
|
<item>saturn</item>
|
||||||
|
<item>uranus</item>
|
||||||
|
<item>neptune</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Keys for planet array information below:
|
||||||
|
"Planet Name",
|
||||||
|
"Navigation icon",
|
||||||
|
"Drawable for planet"
|
||||||
|
"Number of Moons",
|
||||||
|
"Volume"
|
||||||
|
"Surface Area"
|
||||||
|
|
||||||
|
-->
|
||||||
|
<string-array name="mercury">
|
||||||
|
<item>Mercury</item>
|
||||||
|
<item>ic_m_white_48dp</item>
|
||||||
|
<item>mercury</item>
|
||||||
|
<item>0 Moons</item>
|
||||||
|
<item>0.056 x Earth</item>
|
||||||
|
<item>0.147 x Earth</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="venus">
|
||||||
|
<item>Venus</item>
|
||||||
|
<item>ic_v_white_48dp</item>
|
||||||
|
<item>venus</item>
|
||||||
|
<item>0 Moons</item>
|
||||||
|
<item>0.857 x Earth</item>
|
||||||
|
<item>0.902 x Earth</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="earth">
|
||||||
|
<item>Earth</item>
|
||||||
|
<item>ic_e_white_48dp</item>
|
||||||
|
<item>earth</item>
|
||||||
|
<item>1 moon</item>
|
||||||
|
<item>1,083,206,916,846 km3</item>
|
||||||
|
<item>510,064,472 km2</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="mars">
|
||||||
|
<item>Mars</item>
|
||||||
|
<item>ic_m_white_48dp</item>
|
||||||
|
<item>mars</item>
|
||||||
|
<item>2 Moons</item>
|
||||||
|
<item>0.151 x Earth</item>
|
||||||
|
<item>0.283 x Earth</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="jupiter">
|
||||||
|
<item>Jupiter</item>
|
||||||
|
<item>ic_j_white_48dp</item>
|
||||||
|
<item>jupiter</item>
|
||||||
|
<item>67 Moons</item>
|
||||||
|
<item>1,321.337 x Earth</item>
|
||||||
|
<item>120.414 x Earth</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="saturn">
|
||||||
|
<item>Saturn</item>
|
||||||
|
<item>ic_s_white_48dp</item>
|
||||||
|
<item>saturn</item>
|
||||||
|
<item>62 moons</item>
|
||||||
|
<item>763.594 x Earth</item>
|
||||||
|
<item>83.543 x Earth</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="uranus">
|
||||||
|
<item>Uranus</item>
|
||||||
|
<item>ic_u_white_48dp</item>
|
||||||
|
<item>uranus</item>
|
||||||
|
<item>27 Moons</item>
|
||||||
|
<item>63.085 x Earth</item>
|
||||||
|
<item>15.847 x Earth</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="neptune">
|
||||||
|
<item>Neptune</item>
|
||||||
|
<item>ic_n_white_48dp</item>
|
||||||
|
<item>neptune</item>
|
||||||
|
<item>14 Moons</item>
|
||||||
|
<item>57.723 x Earth</item>
|
||||||
|
<item>14.980 x Earth</item>
|
||||||
|
</string-array>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 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.
|
||||||
|
*/
|
||||||
|
package com.example.android.wearable.wear.weardrawers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents planet for app.
|
||||||
|
*/
|
||||||
|
public class Planet {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private String navigationIcon;
|
||||||
|
private String image;
|
||||||
|
private String moons;
|
||||||
|
private String volume;
|
||||||
|
private String surfaceArea;
|
||||||
|
|
||||||
|
public Planet(
|
||||||
|
String name,
|
||||||
|
String navigationIcon,
|
||||||
|
String image,
|
||||||
|
String moons,
|
||||||
|
String volume,
|
||||||
|
String surfaceArea) {
|
||||||
|
|
||||||
|
this.name = name;
|
||||||
|
this.navigationIcon = navigationIcon;
|
||||||
|
this.image = image;
|
||||||
|
this.moons = moons;
|
||||||
|
this.volume = volume;
|
||||||
|
this.surfaceArea = surfaceArea;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNavigationIcon() {
|
||||||
|
return navigationIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getImage() {
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMoons() {
|
||||||
|
return moons;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVolume() {
|
||||||
|
return volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSurfaceArea() {
|
||||||
|
return surfaceArea;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -55,7 +55,16 @@
|
|||||||
|
|
||||||
<service android:name=".service.ListenerService">
|
<service android:name=".service.ListenerService">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
|
<!-- listeners receive events that match the action and data filters -->
|
||||||
|
<action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED" />
|
||||||
|
|
||||||
|
<!-- filters by Constants.CLEAR_NOTIFICATIONS_PATH -->
|
||||||
|
<data android:scheme="wear" android:host="*" android:pathPrefix="/clear"/>
|
||||||
|
|
||||||
|
<!-- filters by Constants.START_PATH, the prefix for
|
||||||
|
Constants.START_ATTRACTION_PATH ('/start/attraction') and
|
||||||
|
Constants.START_NAVIGATION_PATH ('/start/navigation') -->
|
||||||
|
<data android:scheme="wear" android:host="*" android:pathPrefix="/start"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,14 @@
|
|||||||
|
|
||||||
<service android:name=".service.ListenerService">
|
<service android:name=".service.ListenerService">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
|
<!-- listeners receive events that match the action and data filters -->
|
||||||
|
<action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
|
||||||
|
<!-- filters by Constants.EXTRA_ATTRACTIONS -->
|
||||||
|
<data android:scheme="wear" android:host="*" android:pathPrefix="/attraction"/>
|
||||||
|
|
||||||
|
<action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED" />
|
||||||
|
<!-- filters by Constants.CLEAR_NOTIFICATIONS_PATH -->
|
||||||
|
<data android:scheme="wear" android:host="*" android:pathPrefix="/clear"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
|||||||