Update prebuilts for mnc-docs

developers/build: fb039d88ed8e9183cae9ce510bf9c907d23ecc50
developers/samples/android: 7c3c09dcf092593adb8c13f8e15260f4da0a799a

Note: This was previously committed on May 18 2016, but wasn't
automerged. Replaying commit to bring browseable back into sync
downstream.

(cherry picked from commit 8e7e496ae9)
Bug: 29127946
Change-Id: I6275f830532c3c6ce006756aecc3c2ae7e06873d
This commit is contained in:
Trevor Johns
2016-05-18 00:47:32 -07:00
parent 1842261dea
commit 7e9f0673a1
72 changed files with 2033 additions and 680 deletions

View File

@@ -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.DataItem;
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;
/**
@@ -51,7 +49,6 @@ import com.google.android.gms.wearable.Wearable;
* permissions as well.
*/
public class MainActivity extends AppCompatActivity implements
NodeApi.NodeListener,
ConnectionCallbacks,
OnConnectionFailedListener,
ActivityCompat.OnRequestPermissionsResultCallback {
@@ -66,11 +63,11 @@ public class MainActivity extends AppCompatActivity implements
private GoogleApiClient mGoogleApiClient;
private boolean mResolvingError = false;
private TextView mLogTextView;
ScrollView mScroller;
private View mLayout;
private TextView mLogTextView;
private ScrollView mScroller;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -98,10 +95,10 @@ public class MainActivity extends AppCompatActivity implements
@Override
protected void onStop() {
if (mGoogleApiClient.isConnected()) {
Wearable.NodeApi.removeListener(mGoogleApiClient, this);
if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
mGoogleApiClient.disconnect();
}
mGoogleApiClient.disconnect();
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.
* 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)
}
private void pushCalendarToWear() {
startService(new Intent(this, CalendarQueryService.class));
}
public void onDeleteEventsClicked(View view) {
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
public void onConnected(Bundle connectionHint) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Connected to Google Api Service.");
}
mResolvingError = false;
Wearable.NodeApi.addListener(mGoogleApiClient, this);
}
@Override
public void onConnectionSuspended(int cause) {
// Ignore
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onConnectionSuspended(): Cause id: " + cause);
}
}
@Override
@@ -274,9 +260,7 @@ public class MainActivity extends AppCompatActivity implements
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Disconnected from Google Api Service");
}
if (null != Wearable.NodeApi) {
Wearable.NodeApi.removeListener(mGoogleApiClient, this);
}
if (mResolvingError) {
// Already attempting to resolve an error.
return;
@@ -359,4 +343,4 @@ public class MainActivity extends AppCompatActivity implements
}
});
}
}
}

View File

@@ -18,7 +18,7 @@
package="com.example.android.wearable.agendadata" >
<uses-sdk android:minSdkVersion="20"
android:targetSdkVersion="22" />
android:targetSdkVersion="23" />
<uses-feature android:name="android.hardware.type.watch" />
@@ -34,7 +34,8 @@
<service
android:name="com.example.android.wearable.agendadata.HomeListenerService" >
<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>
</service>

View File

@@ -32,4 +32,4 @@ public final class Constants {
public static final String PROFILE_PIC = "profile_pic";
public static final String EXTRA_SILENT = "silent";
}
}

View File

@@ -16,9 +16,6 @@
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.content.Intent;
import android.net.Uri;
@@ -26,6 +23,9 @@ import android.os.Bundle;
import android.support.wearable.activity.ConfirmationActivity;
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.api.GoogleApiClient;
import com.google.android.gms.wearable.DataApi;
@@ -105,14 +105,23 @@ public class DeleteService extends IntentService implements GoogleApiClient.Conn
@Override
public void onConnected(Bundle connectionHint) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onConnected: " + connectionHint);
}
}
@Override
public void onConnectionSuspended(int cause) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onConnectionSuspended: " + cause);
}
}
@Override
public void onConnectionFailed(ConnectionResult result) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onConnectionFailed: " + result);
}
}
}

View File

@@ -16,16 +16,6 @@
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.NotificationManager;
import android.app.PendingIntent;
@@ -37,6 +27,15 @@ import android.text.TextUtils;
import android.text.format.DateFormat;
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.wearable.Asset;
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.DataMap;
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.WearableListenerService;
@@ -63,12 +61,22 @@ public class HomeListenerService extends WearableListenerService {
private static int sNotificationId = 1;
private GoogleApiClient mGoogleApiClient;
@Override
public void onCreate() {
super.onCreate();
mGoogleApiClient = new GoogleApiClient.Builder(this.getApplicationContext())
.addApi(Wearable.API)
.build();
mGoogleApiClient.connect();
}
@Override
public void onDataChanged(DataEventBuffer dataEvents) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onDataChanged: " + dataEvents + " for " + getPackageName());
}
for (DataEvent event : dataEvents) {
if (event.getType() == DataEvent.TYPE_DELETED) {
deleteDataItem(event.getDataItem());
} else if (event.getType() == DataEvent.TYPE_CHANGED) {
@@ -77,13 +85,17 @@ public class HomeListenerService extends WearableListenerService {
}
}
@Override
public void onCreate() {
super.onCreate();
mGoogleApiClient = new GoogleApiClient.Builder(this.getApplicationContext())
.addApi(Wearable.API)
.build();
mGoogleApiClient.connect();
/**
* 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);
}
}
/**
@@ -154,26 +166,4 @@ public class HomeListenerService extends WearableListenerService {
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());
}
}
}

View File

@@ -36,49 +36,57 @@ public class CameraHelper {
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,
* be lenient with the aspect ratio.
*
* @param sizes Supported camera preview sizes.
* @param w The width of the view.
* @param h The height of the view.
* @return Best match camera preview size to fit in the view.
* @param supportedVideoSizes Supported camera video sizes.
* @param previewSizes Supported camera preview sizes.
* @param w The width of 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.
final double ASPECT_TOLERANCE = 0.1;
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;
// 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.
double minDiff = Double.MAX_VALUE;
// Target view height
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
// still maintain the aspect ratio.
for (Camera.Size size : sizes) {
for (Camera.Size size : videoSizes) {
double ratio = (double) size.width / size.height;
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
continue;
if (Math.abs(size.height - targetHeight) < minDiff) {
if (Math.abs(size.height - targetHeight) < minDiff && previewSizes.contains(size)) {
optimalSize = size;
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) {
minDiff = Double.MAX_VALUE;
for (Camera.Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
for (Camera.Size size : videoSizes) {
if (Math.abs(size.height - targetHeight) < minDiff && previewSizes.contains(size)) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}

View File

@@ -36,49 +36,57 @@ public class CameraHelper {
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,
* be lenient with the aspect ratio.
*
* @param sizes Supported camera preview sizes.
* @param w The width of the view.
* @param h The height of the view.
* @return Best match camera preview size to fit in the view.
* @param supportedVideoSizes Supported camera video sizes.
* @param previewSizes Supported camera preview sizes.
* @param w The width of 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.
final double ASPECT_TOLERANCE = 0.1;
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;
// 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.
double minDiff = Double.MAX_VALUE;
// Target view height
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
// still maintain the aspect ratio.
for (Camera.Size size : sizes) {
for (Camera.Size size : videoSizes) {
double ratio = (double) size.width / size.height;
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
continue;
if (Math.abs(size.height - targetHeight) < minDiff) {
if (Math.abs(size.height - targetHeight) < minDiff && previewSizes.contains(size)) {
optimalSize = size;
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) {
minDiff = Double.MAX_VALUE;
for (Camera.Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
for (Camera.Size size : videoSizes) {
if (Math.abs(size.height - targetHeight) < minDiff && previewSizes.contains(size)) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}

View File

@@ -473,7 +473,7 @@ public class BluetoothChatService {
int bytes;
// Keep listening to the InputStream while connected
while (true) {
while (mState == STATE_CONNECTED) {
try {
// Read from the InputStream
bytes = mmInStream.read(buffer);

View File

@@ -275,6 +275,11 @@ public class Camera2BasicFragment extends Fragment
*/
private boolean mFlashSupported;
/**
* Orientation of the camera sensor
*/
private int mSensorOrientation;
/**
* 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
// coordinate.
int displayRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
int sensorOrientation =
characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
//noinspection ConstantConditions
mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
boolean swappedDimensions = false;
switch (displayRotation) {
case Surface.ROTATION_0:
case Surface.ROTATION_180:
if (sensorOrientation == 90 || sensorOrientation == 270) {
if (mSensorOrientation == 90 || mSensorOrientation == 270) {
swappedDimensions = true;
}
break;
case Surface.ROTATION_90:
case Surface.ROTATION_270:
if (sensorOrientation == 0 || sensorOrientation == 180) {
if (mSensorOrientation == 0 || mSensorOrientation == 180) {
swappedDimensions = true;
}
break;
@@ -821,7 +826,7 @@ public class Camera2BasicFragment extends Fragment
// Orientation
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation));
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation));
CameraCaptureSession.CaptureCallback 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
* finished.

View File

@@ -23,6 +23,7 @@
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application android:allowBackup="true"
android:label="@string/app_name"

View File

@@ -55,9 +55,9 @@ import android.view.ViewGroup;
import android.widget.Button;
import android.widget.Toast;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@@ -67,7 +67,10 @@ import java.util.concurrent.TimeUnit;
public class Camera2VideoFragment extends Fragment
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 int REQUEST_VIDEO_PERMISSIONS = 1;
@@ -79,10 +82,17 @@ public class Camera2VideoFragment extends Fragment
};
static {
ORIENTATIONS.append(Surface.ROTATION_0, 90);
ORIENTATIONS.append(Surface.ROTATION_90, 0);
ORIENTATIONS.append(Surface.ROTATION_180, 270);
ORIENTATIONS.append(Surface.ROTATION_270, 180);
DEFAULT_ORIENTATIONS.append(Surface.ROTATION_0, 90);
DEFAULT_ORIENTATIONS.append(Surface.ROTATION_90, 0);
DEFAULT_ORIENTATIONS.append(Surface.ROTATION_180, 270);
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;
/**
* Camera preview.
*/
private CaptureRequest.Builder mPreviewBuilder;
/**
* 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() {
return new Camera2VideoFragment();
@@ -425,6 +434,7 @@ public class Camera2VideoFragment extends Fragment
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
StreamConfigurationMap map = characteristics
.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
mVideoSize = chooseVideoSize(map.getOutputSizes(MediaRecorder.class));
mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
width, height, mVideoSize);
@@ -454,6 +464,7 @@ public class Camera2VideoFragment extends Fragment
private void closeCamera() {
try {
mCameraOpenCloseLock.acquire();
closePreviewSession();
if (null != mCameraDevice) {
mCameraDevice.close();
mCameraDevice = null;
@@ -477,22 +488,16 @@ public class Camera2VideoFragment extends Fragment
return;
}
try {
setUpMediaRecorder();
closePreviewSession();
SurfaceTexture texture = mTextureView.getSurfaceTexture();
assert texture != null;
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
List<Surface> surfaces = new ArrayList<Surface>();
mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
Surface previewSurface = new Surface(texture);
surfaces.add(previewSurface);
mPreviewBuilder.addTarget(previewSurface);
Surface recorderSurface = mMediaRecorder.getSurface();
surfaces.add(recorderSurface);
mPreviewBuilder.addTarget(recorderSurface);
mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {
mCameraDevice.createCaptureSession(Arrays.asList(previewSurface), new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(CameraCaptureSession cameraCaptureSession) {
@@ -510,8 +515,6 @@ public class Camera2VideoFragment extends Fragment
}, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
@@ -575,32 +578,96 @@ public class Camera2VideoFragment extends Fragment
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
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.setVideoFrameRate(30);
mMediaRecorder.setVideoSize(mVideoSize.getWidth(), mVideoSize.getHeight());
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
int orientation = ORIENTATIONS.get(rotation);
mMediaRecorder.setOrientationHint(orientation);
switch (mSensorOrientation) {
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();
}
private File getVideoFile(Context context) {
return new File(context.getExternalFilesDir(null), "video.mp4");
private String getVideoFilePath(Context context) {
return context.getExternalFilesDir(null).getAbsolutePath() + "/"
+ System.currentTimeMillis() + ".mp4";
}
private void startRecordingVideo() {
if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) {
return;
}
try {
// UI
mButtonVideo.setText(R.string.stop);
mIsRecordingVideo = true;
closePreviewSession();
setUpMediaRecorder();
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
mMediaRecorder.start();
} catch (IllegalStateException e) {
// Set up Surface for the camera preview
Surface previewSurface = new Surface(texture);
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();
} 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
mMediaRecorder.stop();
mMediaRecorder.reset();
Activity activity = getActivity();
if (null != activity) {
Toast.makeText(activity, "Video saved: " + getVideoFile(activity),
Toast.makeText(activity, "Video saved: " + mNextVideoAbsolutePath,
Toast.LENGTH_SHORT).show();
Log.d(TAG, "Video saved: " + mNextVideoAbsolutePath);
}
mNextVideoAbsolutePath = null;
startPreview();
}
@@ -687,4 +757,4 @@ public class Camera2VideoFragment extends Fragment
}
}
}

View File

@@ -22,9 +22,9 @@ import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.provider.MediaStore;
import android.util.Log;
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.OnConnectionFailedListener;
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.CapabilityApi;
import com.google.android.gms.wearable.CapabilityInfo;
import com.google.android.gms.wearable.DataApi;
import com.google.android.gms.wearable.DataApi.DataItemResult;
import com.google.android.gms.wearable.DataEvent;
@@ -61,7 +62,6 @@ import java.io.IOException;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
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
* to the paired wearable.
*/
public class MainActivity extends Activity implements DataApi.DataListener,
MessageApi.MessageListener, NodeApi.NodeListener, ConnectionCallbacks,
public class MainActivity extends Activity implements
CapabilityApi.CapabilityListener,
MessageApi.MessageListener,
DataApi.DataListener,
ConnectionCallbacks,
OnConnectionFailedListener {
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_IMAGE_CAPTURE = 1;
private static final String START_ACTIVITY_PATH = "/start-activity";
private static final String COUNT_PATH = "/count";
private static final String IMAGE_PATH = "/image";
@@ -100,18 +103,14 @@ public class MainActivity extends Activity implements DataApi.DataListener,
private View mStartActivityBtn;
private DataItemAdapter mDataItemListAdapter;
private Handler mHandler;
// Send DataItems.
private ScheduledExecutorService mGeneratorExecutor;
private ScheduledFuture<?> mDataItemGeneratorFuture;
static final int REQUEST_IMAGE_CAPTURE = 1;
@Override
public void onCreate(Bundle b) {
super.onCreate(b);
mHandler = new Handler();
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LOGD(TAG, "onCreate");
mCameraSupported = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
setContentView(R.layout.main_activity);
@@ -130,15 +129,6 @@ public class MainActivity extends Activity implements DataApi.DataListener,
.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
protected void onStart() {
super.onStart();
@@ -162,16 +152,25 @@ public class MainActivity extends Activity implements DataApi.DataListener,
@Override
protected void onStop() {
if (!mResolvingError) {
if (!mResolvingError && (mGoogleApiClient != null) && (mGoogleApiClient.isConnected())) {
Wearable.DataApi.removeListener(mGoogleApiClient, this);
Wearable.MessageApi.removeListener(mGoogleApiClient, this);
Wearable.NodeApi.removeListener(mGoogleApiClient, this);
Wearable.CapabilityApi.removeListener(mGoogleApiClient, this);
mGoogleApiClient.disconnect();
}
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) {
LOGD(TAG, "Google API Client was connected");
mResolvingError = false;
@@ -179,157 +178,100 @@ public class MainActivity extends Activity implements DataApi.DataListener,
mSendPhotoBtn.setEnabled(mCameraSupported);
Wearable.DataApi.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) {
LOGD(TAG, "Connection to Google API client was suspended");
mStartActivityBtn.setEnabled(false);
mSendPhotoBtn.setEnabled(false);
}
@Override //OnConnectionFailedListener
@Override
public void onConnectionFailed(ConnectionResult result) {
if (mResolvingError) {
// Already attempting to resolve an error.
return;
} else if (result.hasResolution()) {
try {
mResolvingError = true;
result.startResolutionForResult(this, REQUEST_RESOLVE_ERROR);
} catch (IntentSender.SendIntentException e) {
// There was an error with the resolution intent. Try again.
mGoogleApiClient.connect();
if (!mResolvingError) {
if (result.hasResolution()) {
try {
mResolvingError = true;
result.startResolutionForResult(this, REQUEST_RESOLVE_ERROR);
} catch (IntentSender.SendIntentException e) {
// There was an error with the resolution intent. Try again.
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) {
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);
runOnUiThread(new Runnable() {
@Override
public void run() {
for (DataEvent event : events) {
if (event.getType() == DataEvent.TYPE_CHANGED) {
mDataItemListAdapter.add(
new Event("DataItem Changed", event.getDataItem().toString()));
} else if (event.getType() == DataEvent.TYPE_DELETED) {
mDataItemListAdapter.add(
new Event("DataItem Deleted", event.getDataItem().toString()));
}
}
for (DataEvent event : dataEvents) {
if (event.getType() == DataEvent.TYPE_CHANGED) {
mDataItemListAdapter.add(
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) {
LOGD(TAG, "onMessageReceived() A message from watch was received:" + messageEvent
.getRequestId() + " " + messageEvent.getPath());
mHandler.post(new Runnable() {
@Override
public void run() {
mDataItemListAdapter.add(new Event("Message from watch", messageEvent.toString()));
}
});
LOGD(TAG, "onMessageReceived() A message from watch was received:"
+ messageEvent.getRequestId() + " " + messageEvent.getPath());
mDataItemListAdapter.add(new Event("Message from watch", messageEvent.toString()));
}
@Override //NodeListener
public void onPeerConnected(final Node peer) {
LOGD(TAG, "onPeerConnected: " + peer);
mHandler.post(new Runnable() {
@Override
public void run() {
mDataItemListAdapter.add(new Event("Connected", peer.toString()));
}
});
@Override
public void onCapabilityChanged(final CapabilityInfo capabilityInfo) {
LOGD(TAG, "onCapabilityChanged: " + capabilityInfo);
}
@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()));
}
});
mDataItemListAdapter.add(new Event("onCapabilityChanged", capabilityInfo.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) {
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;
public void onSendPhotoClick(View view) {
if (null != mImageBitmap && mGoogleApiClient.isConnected()) {
sendPhoto(toAsset(mImageBitmap));
}
}
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;
String text;
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;
// 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();
}
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
* 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) {
PutDataMapRequest dataMap = PutDataMapRequest.create(IMAGE_PATH);
@@ -454,27 +341,18 @@ public class MainActivity extends Activity implements DataApi.DataListener,
.isSuccess());
}
});
}
public void onTakePhotoClick(View view) {
dispatchTakePictureIntent();
}
private Collection<String> getNodes() {
HashSet<String> results = new HashSet<>();
NodeApi.GetConnectedNodesResult nodes =
Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).await();
public void onSendPhotoClick(View view) {
if (null != mImageBitmap && mGoogleApiClient.isConnected()) {
sendPhoto(toAsset(mImageBitmap));
for (Node node : nodes.getNodes()) {
results.add(node.getId());
}
}
/**
* 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);
return results;
}
/**
@@ -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());
}
}
});
}
}
}

View File

@@ -18,7 +18,7 @@
package="com.example.android.wearable.datalayer" >
<uses-sdk android:minSdkVersion="20"
android:targetSdkVersion="22" />
android:targetSdkVersion="23" />
<uses-feature android:name="android.hardware.type.watch" />
@@ -35,10 +35,15 @@
<service
android:name=".DataLayerListenerService" >
<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>
</service>
<activity
android:name=".MainActivity"
android:label="@string/app_name">
@@ -52,4 +57,4 @@
</intent-filter>
</activity>
</application>
</manifest>
</manifest>

View File

@@ -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.DataEventBuffer;
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.WearableListenerService;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
@@ -37,7 +35,7 @@ import java.util.concurrent.TimeUnit;
*/
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 DATA_ITEM_RECEIVED_PATH = "/data-item-received";
@@ -98,19 +96,9 @@ 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) {
if (Log.isLoggable(tag, Log.DEBUG)) {
Log.d(tag, message);
}
}
}
}

View File

@@ -23,9 +23,9 @@ import android.app.Fragment;
import android.app.FragmentManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.support.wearable.view.DotsPageIndicator;
import android.support.wearable.view.FragmentGridPagerAdapter;
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.MessageEvent;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.Wearable;
import java.io.InputStream;
@@ -78,9 +77,12 @@ import java.util.Set;
* </li>
* </ul>
*/
public class MainActivity extends Activity implements ConnectionCallbacks,
OnConnectionFailedListener, DataApi.DataListener, MessageApi.MessageListener,
NodeApi.NodeListener {
public class MainActivity extends Activity implements
ConnectionCallbacks,
OnConnectionFailedListener,
DataApi.DataListener,
MessageApi.MessageListener,
CapabilityApi.CapabilityListener {
private static final String TAG = "MainActivity";
private static final String CAPABILITY_1_NAME = "capability_1";
@@ -92,8 +94,8 @@ public class MainActivity extends Activity implements ConnectionCallbacks,
private AssetFragment mAssetFragment;
@Override
public void onCreate(Bundle b) {
super.onCreate(b);
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setupViews();
@@ -112,11 +114,14 @@ public class MainActivity extends Activity implements ConnectionCallbacks,
@Override
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();
Wearable.DataApi.removeListener(mGoogleApiClient, this);
Wearable.MessageApi.removeListener(mGoogleApiClient, this);
Wearable.NodeApi.removeListener(mGoogleApiClient, this);
mGoogleApiClient.disconnect();
}
@Override
@@ -124,7 +129,8 @@ public class MainActivity extends Activity implements ConnectionCallbacks,
LOGD(TAG, "onConnected(): Successfully connected to Google API client");
Wearable.DataApi.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
@@ -244,13 +250,9 @@ public class MainActivity extends Activity implements ConnectionCallbacks,
}
@Override
public void onPeerConnected(Node node) {
mDataFragment.appendItem("Node Connected", node.getId());
}
@Override
public void onPeerDisconnected(Node node) {
mDataFragment.appendItem("Node Disconnected", node.getId());
public void onCapabilityChanged(CapabilityInfo capabilityInfo) {
LOGD(TAG, "onCapabilityChanged: " + capabilityInfo);
mDataFragment.appendItem("onCapabilityChanged", capabilityInfo.toString());
}
private void setupViews() {
@@ -312,7 +314,7 @@ public class MainActivity extends Activity implements ConnectionCallbacks,
@Override
protected Bitmap doInBackground(Asset... params) {
if(params.length > 0) {
if (params.length > 0) {
Asset asset = params[0];
@@ -334,11 +336,11 @@ public class MainActivity extends Activity implements ConnectionCallbacks,
@Override
protected void onPostExecute(Bitmap bitmap) {
if(bitmap != null) {
if (bitmap != null) {
LOGD(TAG, "Setting background image on second page..");
moveToPage(1);
mAssetFragment.setBackgroundImage(bitmap);
}
}
}
}
}

View File

@@ -36,49 +36,57 @@ public class CameraHelper {
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,
* be lenient with the aspect ratio.
*
* @param sizes Supported camera preview sizes.
* @param w The width of the view.
* @param h The height of the view.
* @return Best match camera preview size to fit in the view.
* @param supportedVideoSizes Supported camera video sizes.
* @param previewSizes Supported camera preview sizes.
* @param w The width of 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.
final double ASPECT_TOLERANCE = 0.1;
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;
// 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.
double minDiff = Double.MAX_VALUE;
// Target view height
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
// still maintain the aspect ratio.
for (Camera.Size size : sizes) {
for (Camera.Size size : videoSizes) {
double ratio = (double) size.width / size.height;
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
continue;
if (Math.abs(size.height - targetHeight) < minDiff) {
if (Math.abs(size.height - targetHeight) < minDiff && previewSizes.contains(size)) {
optimalSize = size;
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) {
minDiff = Double.MAX_VALUE;
for (Camera.Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
for (Camera.Size size : videoSizes) {
if (Math.abs(size.height - targetHeight) < minDiff && previewSizes.contains(size)) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}

View File

@@ -32,6 +32,7 @@ import android.widget.Button;
import com.example.android.common.media.CameraHelper;
import java.io.File;
import java.io.IOException;
import java.util.List;
@@ -45,6 +46,7 @@ public class MainActivity extends Activity {
private Camera mCamera;
private TextureView mPreview;
private MediaRecorder mMediaRecorder;
private File mOutputFile;
private boolean isRecording = false;
private static final String TAG = "Recorder";
@@ -71,7 +73,15 @@ public class MainActivity extends Activity {
// BEGIN_INCLUDE(stop_release_media_recorder)
// 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
mCamera.lock(); // take camera access back from MediaRecorder
@@ -137,8 +147,9 @@ public class MainActivity extends Activity {
// dimensions of our preview surface.
Camera.Parameters parameters = mCamera.getParameters();
List<Camera.Size> mSupportedPreviewSizes = parameters.getSupportedPreviewSizes();
Camera.Size optimalSize = CameraHelper.getOptimalPreviewSize(mSupportedPreviewSizes,
mPreview.getWidth(), mPreview.getHeight());
List<Camera.Size> mSupportedVideoSizes = parameters.getSupportedVideoSizes();
Camera.Size optimalSize = CameraHelper.getOptimalVideoSize(mSupportedVideoSizes,
mSupportedPreviewSizes, mPreview.getWidth(), mPreview.getHeight());
// Use the same size for recording profile.
CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
@@ -174,8 +185,11 @@ public class MainActivity extends Activity {
mMediaRecorder.setProfile(profile);
// Step 4: Set output file
mMediaRecorder.setOutputFile(CameraHelper.getOutputMediaFile(
CameraHelper.MEDIA_TYPE_VIDEO).toString());
mOutputFile = CameraHelper.getOutputMediaFile(CameraHelper.MEDIA_TYPE_VIDEO);
if (mOutputFile == null) {
return false;
}
mMediaRecorder.setOutputFile(mOutputFile.getPath());
// END_INCLUDE (configure_media_recorder)
// Step 5: Prepare configured MediaRecorder
@@ -227,4 +241,4 @@ public class MainActivity extends Activity {
}
}
}
}

View File

@@ -66,7 +66,8 @@
android:enabled="true"
android:exported="true" >
<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>
</service>
</application>

View File

@@ -61,7 +61,8 @@
android:enabled="true"
android:exported="true" >
<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>
</service>
</application>

View File

@@ -48,7 +48,10 @@
</activity>
<service android:name=".db.UpdateService">
<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>
</service>
</application>

View File

@@ -18,7 +18,7 @@
package="com.example.android.wearable.speedtracker">
<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.WAKE_LOCK" />

View File

@@ -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>

View File

@@ -330,6 +330,13 @@ public class WearableMainActivity extends WearableActivity implements
public void onConnected(Bundle bundle) {
Log.d(TAG, "onConnected()");
requestLocation();
}
private void requestLocation() {
Log.d(TAG, "requestLocation()");
/*
* 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)) {
Log.i(TAG, "GPS permission granted.");
mGpsPermissionApproved = true;
if(mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
requestLocation();
}
} else {
Log.i(TAG, "GPS permission NOT granted.");
mGpsPermissionApproved = false;

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -42,8 +42,10 @@
</activity>
<service android:name=".DismissListener">
<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.BOTH_PATH ('/both') -->
<data android:scheme="wear" android:host="*" android:pathPrefix="/both"/>
</intent-filter>
<intent-filter>
<action

View File

@@ -41,8 +41,13 @@
</activity>
<service android:name=".NotificationUpdateService">
<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.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>
<action

View File

@@ -132,6 +132,42 @@
<category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
</intent-filter>
</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
android:name=".InteractiveWatchFaceService"
android:label="@string/interactive_name"
@@ -218,7 +254,8 @@
</service>
<service android:name=".DigitalWatchFaceConfigListenerService" >
<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>
</service>
<service
@@ -269,6 +306,35 @@
android:name=".CalendarWatchFacePermissionActivity"
android:label="@string/title_activity_calendar_watch_face_permission" >
</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>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@@ -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>

View File

@@ -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>

View 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>

View File

@@ -18,6 +18,7 @@
<string name="opengl_name">Sample OpenGL</string>
<string name="interactive_name">Sample Interactive</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="card_bounds_name">Sample Card Bounds</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="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) -->
<string name="color_black">Black</string>
<string name="color_blue">Blue</string>

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -40,6 +40,11 @@ public class DigitalWatchFaceConfigListenerService extends WearableListenerServi
@Override // WearableListenerService
public void onMessageReceived(MessageEvent messageEvent) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onMessageReceived: " + messageEvent);
}
if (!messageEvent.getPath().equals(DigitalWatchFaceUtil.PATH_WITH_FEATURE)) {
return;
}

View File

@@ -62,7 +62,10 @@ import java.util.concurrent.TimeUnit;
*
* 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
* 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.
*

View File

@@ -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);
}
}

View 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>

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 940 B

View File

@@ -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"/>

View File

@@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

View 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>

View File

@@ -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;
}
}

View File

@@ -55,7 +55,16 @@
<service android:name=".service.ListenerService">
<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>
</service>

View File

@@ -44,7 +44,14 @@
<service android:name=".service.ListenerService">
<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>
</service>