diff --git a/samples/browseable/AgendaData/Application/src/com.example.android.wearable.agendadata/MainActivity.java b/samples/browseable/AgendaData/Application/src/com.example.android.wearable.agendadata/MainActivity.java index 6a9678a66..1492c813f 100644 --- a/samples/browseable/AgendaData/Application/src/com.example.android.wearable.agendadata/MainActivity.java +++ b/samples/browseable/AgendaData/Application/src/com.example.android.wearable.agendadata/MainActivity.java @@ -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 } }); } -} +} \ No newline at end of file diff --git a/samples/browseable/AgendaData/Wearable/AndroidManifest.xml b/samples/browseable/AgendaData/Wearable/AndroidManifest.xml index e6dbab705..87bed7061 100644 --- a/samples/browseable/AgendaData/Wearable/AndroidManifest.xml +++ b/samples/browseable/AgendaData/Wearable/AndroidManifest.xml @@ -18,7 +18,7 @@ package="com.example.android.wearable.agendadata" > + android:targetSdkVersion="23" /> @@ -34,7 +34,8 @@ - + + diff --git a/samples/browseable/AgendaData/Wearable/src/com.example.android.wearable.agendadata/Constants.java b/samples/browseable/AgendaData/Wearable/src/com.example.android.wearable.agendadata/Constants.java index 176a21eb1..aa15b6a85 100644 --- a/samples/browseable/AgendaData/Wearable/src/com.example.android.wearable.agendadata/Constants.java +++ b/samples/browseable/AgendaData/Wearable/src/com.example.android.wearable.agendadata/Constants.java @@ -32,4 +32,4 @@ public final class Constants { public static final String PROFILE_PIC = "profile_pic"; public static final String EXTRA_SILENT = "silent"; -} +} \ No newline at end of file diff --git a/samples/browseable/AgendaData/Wearable/src/com.example.android.wearable.agendadata/DeleteService.java b/samples/browseable/AgendaData/Wearable/src/com.example.android.wearable.agendadata/DeleteService.java index 86aafe03d..bc4180e9b 100644 --- a/samples/browseable/AgendaData/Wearable/src/com.example.android.wearable.agendadata/DeleteService.java +++ b/samples/browseable/AgendaData/Wearable/src/com.example.android.wearable.agendadata/DeleteService.java @@ -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); + } } } diff --git a/samples/browseable/AgendaData/Wearable/src/com.example.android.wearable.agendadata/HomeListenerService.java b/samples/browseable/AgendaData/Wearable/src/com.example.android.wearable.agendadata/HomeListenerService.java index 02a386116..909c27d99 100644 --- a/samples/browseable/AgendaData/Wearable/src/com.example.android.wearable.agendadata/HomeListenerService.java +++ b/samples/browseable/AgendaData/Wearable/src/com.example.android.wearable.agendadata/HomeListenerService.java @@ -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()); - } - } - } diff --git a/samples/browseable/BasicMediaDecoder/src/com.example.android.common.media/CameraHelper.java b/samples/browseable/BasicMediaDecoder/src/com.example.android.common.media/CameraHelper.java index 1fa841675..b578767db 100644 --- a/samples/browseable/BasicMediaDecoder/src/com.example.android.common.media/CameraHelper.java +++ b/samples/browseable/BasicMediaDecoder/src/com.example.android.common.media/CameraHelper.java @@ -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 sizes, int w, int h) { + public static Camera.Size getOptimalVideoSize(List supportedVideoSizes, + List 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 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); } diff --git a/samples/browseable/BasicRenderScript/src/com.example.android.common.media/CameraHelper.java b/samples/browseable/BasicRenderScript/src/com.example.android.common.media/CameraHelper.java index 1fa841675..b578767db 100644 --- a/samples/browseable/BasicRenderScript/src/com.example.android.common.media/CameraHelper.java +++ b/samples/browseable/BasicRenderScript/src/com.example.android.common.media/CameraHelper.java @@ -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 sizes, int w, int h) { + public static Camera.Size getOptimalVideoSize(List supportedVideoSizes, + List 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 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); } diff --git a/samples/browseable/BluetoothChat/src/com.example.android.bluetoothchat/BluetoothChatService.java b/samples/browseable/BluetoothChat/src/com.example.android.bluetoothchat/BluetoothChatService.java index b88b160d2..a1e7cc016 100644 --- a/samples/browseable/BluetoothChat/src/com.example.android.bluetoothchat/BluetoothChatService.java +++ b/samples/browseable/BluetoothChat/src/com.example.android.bluetoothchat/BluetoothChatService.java @@ -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); diff --git a/samples/browseable/Camera2Basic/src/com.example.android.camera2basic/Camera2BasicFragment.java b/samples/browseable/Camera2Basic/src/com.example.android.camera2basic/Camera2BasicFragment.java index 747d8d83b..a29fa1449 100644 --- a/samples/browseable/Camera2Basic/src/com.example.android.camera2basic/Camera2BasicFragment.java +++ b/samples/browseable/Camera2Basic/src/com.example.android.camera2basic/Camera2BasicFragment.java @@ -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. diff --git a/samples/browseable/Camera2Video/AndroidManifest.xml b/samples/browseable/Camera2Video/AndroidManifest.xml index 5cb54286c..67a90d061 100644 --- a/samples/browseable/Camera2Video/AndroidManifest.xml +++ b/samples/browseable/Camera2Video/AndroidManifest.xml @@ -23,6 +23,7 @@ + surfaces = new ArrayList(); + 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 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 } -} +} \ No newline at end of file diff --git a/samples/browseable/DataLayer/Application/src/com.example.android.wearable.datalayer/MainActivity.java b/samples/browseable/DataLayer/Application/src/com.example.android.wearable.datalayer/MainActivity.java index e559db683..ca86d7a30 100644 --- a/samples/browseable/DataLayer/Application/src/com.example.android.wearable.datalayer/MainActivity.java +++ b/samples/browseable/DataLayer/Application/src/com.example.android.wearable.datalayer/MainActivity.java @@ -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 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 { + 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 getNodes() { - HashSet 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 { - - @Override - protected Void doInBackground(Void... args) { - Collection 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() { - @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 getNodes() { + HashSet 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 { + + 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 { + + @Override + protected Void doInBackground(Void... args) { + Collection 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() { + @Override + public void onResult(DataItemResult dataItemResult) { + if (!dataItemResult.getStatus().isSuccess()) { + Log.e(TAG, "ERROR: failed to putDataItem, status code: " + + dataItemResult.getStatus().getStatusCode()); + } + } + }); + } + } +} \ No newline at end of file diff --git a/samples/browseable/DataLayer/Wearable/AndroidManifest.xml b/samples/browseable/DataLayer/Wearable/AndroidManifest.xml index 5567365db..6608bb705 100644 --- a/samples/browseable/DataLayer/Wearable/AndroidManifest.xml +++ b/samples/browseable/DataLayer/Wearable/AndroidManifest.xml @@ -18,7 +18,7 @@ package="com.example.android.wearable.datalayer" > + android:targetSdkVersion="23" /> @@ -35,10 +35,15 @@ - + + + + + + + - @@ -52,4 +57,4 @@ - + \ No newline at end of file diff --git a/samples/browseable/DataLayer/Wearable/src/com.example.android.wearable.datalayer/DataLayerListenerService.java b/samples/browseable/DataLayer/Wearable/src/com.example.android.wearable.datalayer/DataLayerListenerService.java index 67dcef94f..020cd1588 100644 --- a/samples/browseable/DataLayer/Wearable/src/com.example.android.wearable.datalayer/DataLayerListenerService.java +++ b/samples/browseable/DataLayer/Wearable/src/com.example.android.wearable.datalayer/DataLayerListenerService.java @@ -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); } } -} +} \ No newline at end of file diff --git a/samples/browseable/DataLayer/Wearable/src/com.example.android.wearable.datalayer/MainActivity.java b/samples/browseable/DataLayer/Wearable/src/com.example.android.wearable.datalayer/MainActivity.java index b3cb25304..402912f44 100644 --- a/samples/browseable/DataLayer/Wearable/src/com.example.android.wearable.datalayer/MainActivity.java +++ b/samples/browseable/DataLayer/Wearable/src/com.example.android.wearable.datalayer/MainActivity.java @@ -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; * * */ -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); } } } -} +} \ No newline at end of file diff --git a/samples/browseable/MediaRecorder/src/com.example.android.common.media/CameraHelper.java b/samples/browseable/MediaRecorder/src/com.example.android.common.media/CameraHelper.java index 1fa841675..b578767db 100644 --- a/samples/browseable/MediaRecorder/src/com.example.android.common.media/CameraHelper.java +++ b/samples/browseable/MediaRecorder/src/com.example.android.common.media/CameraHelper.java @@ -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 sizes, int w, int h) { + public static Camera.Size getOptimalVideoSize(List supportedVideoSizes, + List 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 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); } diff --git a/samples/browseable/MediaRecorder/src/com.example.android.mediarecorder/MainActivity.java b/samples/browseable/MediaRecorder/src/com.example.android.mediarecorder/MainActivity.java index 85876363b..102ff4d6a 100644 --- a/samples/browseable/MediaRecorder/src/com.example.android.mediarecorder/MainActivity.java +++ b/samples/browseable/MediaRecorder/src/com.example.android.mediarecorder/MainActivity.java @@ -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 mSupportedPreviewSizes = parameters.getSupportedPreviewSizes(); - Camera.Size optimalSize = CameraHelper.getOptimalPreviewSize(mSupportedPreviewSizes, - mPreview.getWidth(), mPreview.getHeight()); + List 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 { } } -} \ No newline at end of file +} diff --git a/samples/browseable/RuntimePermissionsWear/Application/AndroidManifest.xml b/samples/browseable/RuntimePermissionsWear/Application/AndroidManifest.xml index 861cad3b4..d8a6a2803 100644 --- a/samples/browseable/RuntimePermissionsWear/Application/AndroidManifest.xml +++ b/samples/browseable/RuntimePermissionsWear/Application/AndroidManifest.xml @@ -66,7 +66,8 @@ android:enabled="true" android:exported="true" > - + + diff --git a/samples/browseable/RuntimePermissionsWear/Wearable/AndroidManifest.xml b/samples/browseable/RuntimePermissionsWear/Wearable/AndroidManifest.xml index 43218d789..e654253fc 100644 --- a/samples/browseable/RuntimePermissionsWear/Wearable/AndroidManifest.xml +++ b/samples/browseable/RuntimePermissionsWear/Wearable/AndroidManifest.xml @@ -61,7 +61,8 @@ android:enabled="true" android:exported="true" > - + + diff --git a/samples/browseable/SpeedTracker/Application/AndroidManifest.xml b/samples/browseable/SpeedTracker/Application/AndroidManifest.xml index be88f6d68..debd11d6f 100644 --- a/samples/browseable/SpeedTracker/Application/AndroidManifest.xml +++ b/samples/browseable/SpeedTracker/Application/AndroidManifest.xml @@ -48,7 +48,10 @@ - + + + + diff --git a/samples/browseable/SpeedTracker/Wearable/AndroidManifest.xml b/samples/browseable/SpeedTracker/Wearable/AndroidManifest.xml index c9cbad183..e120686e5 100644 --- a/samples/browseable/SpeedTracker/Wearable/AndroidManifest.xml +++ b/samples/browseable/SpeedTracker/Wearable/AndroidManifest.xml @@ -18,7 +18,7 @@ package="com.example.android.wearable.speedtracker"> - + diff --git a/samples/browseable/SpeedTracker/Wearable/res/layout/saving_activity.xml b/samples/browseable/SpeedTracker/Wearable/res/layout/saving_activity.xml deleted file mode 100644 index c37d95930..000000000 --- a/samples/browseable/SpeedTracker/Wearable/res/layout/saving_activity.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/samples/browseable/SpeedTracker/Wearable/src/com.example.android.wearable.speedtracker/WearableMainActivity.java b/samples/browseable/SpeedTracker/Wearable/src/com.example.android.wearable.speedtracker/WearableMainActivity.java index 25f424cac..66550c6c5 100644 --- a/samples/browseable/SpeedTracker/Wearable/src/com.example.android.wearable.speedtracker/WearableMainActivity.java +++ b/samples/browseable/SpeedTracker/Wearable/src/com.example.android.wearable.speedtracker/WearableMainActivity.java @@ -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; diff --git a/samples/browseable/SpeedTracker/Wearable/src/com.example.android.wearable.speedtracker/ui/LocationSettingActivity.java b/samples/browseable/SpeedTracker/Wearable/src/com.example.android.wearable.speedtracker/ui/LocationSettingActivity.java deleted file mode 100644 index 1f8be71c2..000000000 --- a/samples/browseable/SpeedTracker/Wearable/src/com.example.android.wearable.speedtracker/ui/LocationSettingActivity.java +++ /dev/null @@ -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(); - - } -} diff --git a/samples/browseable/StorageProvider/src/com.example.android.storageprovider/MyCloudFragment.java b/samples/browseable/StorageProvider/src/com.example.android.storageprovider/MyCloudFragment.java deleted file mode 100644 index f624e908a..000000000 --- a/samples/browseable/StorageProvider/src/com.example.android.storageprovider/MyCloudFragment.java +++ /dev/null @@ -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); - } - -} - - diff --git a/samples/browseable/SynchronizedNotifications/Application/AndroidManifest.xml b/samples/browseable/SynchronizedNotifications/Application/AndroidManifest.xml index 04a69e01b..b2cba0c91 100644 --- a/samples/browseable/SynchronizedNotifications/Application/AndroidManifest.xml +++ b/samples/browseable/SynchronizedNotifications/Application/AndroidManifest.xml @@ -42,8 +42,10 @@ - + + + + - + + + + + + ; + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + diff --git a/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_complication_simple.png b/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_complication_simple.png new file mode 100644 index 000000000..48d25ef8c Binary files /dev/null and b/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_complication_simple.png differ diff --git a/samples/browseable/WatchFace/Wearable/res/drawable-xhdpi/complications_left_dial.png b/samples/browseable/WatchFace/Wearable/res/drawable-xhdpi/complications_left_dial.png new file mode 100644 index 000000000..93bb31ca7 Binary files /dev/null and b/samples/browseable/WatchFace/Wearable/res/drawable-xhdpi/complications_left_dial.png differ diff --git a/samples/browseable/WatchFace/Wearable/res/drawable-xhdpi/complications_right_dial.png b/samples/browseable/WatchFace/Wearable/res/drawable-xhdpi/complications_right_dial.png new file mode 100644 index 000000000..5db3c9828 Binary files /dev/null and b/samples/browseable/WatchFace/Wearable/res/drawable-xhdpi/complications_right_dial.png differ diff --git a/samples/browseable/WatchFace/Wearable/res/layout/activity_complication_simple_config.xml b/samples/browseable/WatchFace/Wearable/res/layout/activity_complication_simple_config.xml new file mode 100644 index 000000000..01f5cac74 --- /dev/null +++ b/samples/browseable/WatchFace/Wearable/res/layout/activity_complication_simple_config.xml @@ -0,0 +1,38 @@ + + + + + + + + + \ No newline at end of file diff --git a/samples/browseable/WatchFace/Wearable/res/layout/activity_complication_simple_list_item.xml b/samples/browseable/WatchFace/Wearable/res/layout/activity_complication_simple_list_item.xml new file mode 100644 index 000000000..e99527187 --- /dev/null +++ b/samples/browseable/WatchFace/Wearable/res/layout/activity_complication_simple_list_item.xml @@ -0,0 +1,34 @@ + + + + + + + \ No newline at end of file diff --git a/samples/browseable/WatchFace/Wearable/res/values/arrays.xml b/samples/browseable/WatchFace/Wearable/res/values/arrays.xml new file mode 100644 index 000000000..a2e37acb0 --- /dev/null +++ b/samples/browseable/WatchFace/Wearable/res/values/arrays.xml @@ -0,0 +1,26 @@ + + + + + Left dial + Right dial + + + @drawable/complications_left_dial + @drawable/complications_right_dial + + \ No newline at end of file diff --git a/samples/browseable/WatchFace/Wearable/res/values/strings.xml b/samples/browseable/WatchFace/Wearable/res/values/strings.xml index 4090995de..e4b27173d 100644 --- a/samples/browseable/WatchFace/Wearable/res/values/strings.xml +++ b/samples/browseable/WatchFace/Wearable/res/values/strings.xml @@ -18,6 +18,7 @@ Sample OpenGL Sample Interactive Sample Analog + Sample Complication Simple Sample Sweep Sample Card Bounds Sample Digital @@ -42,6 +43,9 @@ Calendar Permission Activity WatchFace requires Calendar access. + Configuration + Random Number + Black Blue diff --git a/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/ComplicationSimpleConfigActivity.java b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/ComplicationSimpleConfigActivity.java new file mode 100644 index 000000000..66e208c2e --- /dev/null +++ b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/ComplicationSimpleConfigActivity.java @@ -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 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 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 mItems; + + + public ConfigurationAdapter (Context context, List 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); + } + } + +} \ No newline at end of file diff --git a/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/ComplicationSimpleWatchFaceService.java b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/ComplicationSimpleWatchFaceService.java new file mode 100644 index 000000000..9eca2c33b --- /dev/null +++ b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/ComplicationSimpleWatchFaceService.java @@ -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 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; + } + } +} \ No newline at end of file diff --git a/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/DigitalWatchFaceConfigListenerService.java b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/DigitalWatchFaceConfigListenerService.java index 725c51aa8..8d99de6cb 100644 --- a/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/DigitalWatchFaceConfigListenerService.java +++ b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/DigitalWatchFaceConfigListenerService.java @@ -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; } diff --git a/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/FitDistanceWatchFaceService.java b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/FitDistanceWatchFaceService.java index b29a1902d..6dee04e62 100644 --- a/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/FitDistanceWatchFaceService.java +++ b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/FitDistanceWatchFaceService.java @@ -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. * diff --git a/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/provider/RandomNumberProviderService.java b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/provider/RandomNumberProviderService.java new file mode 100644 index 000000000..916f90fd9 --- /dev/null +++ b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/provider/RandomNumberProviderService.java @@ -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); + } +} \ No newline at end of file diff --git a/samples/browseable/WearDrawers/AndroidManifest.xml b/samples/browseable/WearDrawers/AndroidManifest.xml new file mode 100644 index 000000000..3044d1906 --- /dev/null +++ b/samples/browseable/WearDrawers/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/browseable/WearDrawers/_index.jd b/samples/browseable/WearDrawers/_index.jd new file mode 100644 index 000000000..93327b8f5 --- /dev/null +++ b/samples/browseable/WearDrawers/_index.jd @@ -0,0 +1,10 @@ + +page.tags="WearDrawers" +sample.group=Wearable +@jd:body + +

+ +A simple sample that demonstrates Navigation and Action Drawers, part of Material Design for Wear. + +

diff --git a/samples/browseable/WearDrawers/res/drawable-hdpi/earth.png b/samples/browseable/WearDrawers/res/drawable-hdpi/earth.png new file mode 100644 index 000000000..13abd77a5 Binary files /dev/null and b/samples/browseable/WearDrawers/res/drawable-hdpi/earth.png differ diff --git a/samples/browseable/WearDrawers/res/drawable-hdpi/ic_e_white_48dp.png b/samples/browseable/WearDrawers/res/drawable-hdpi/ic_e_white_48dp.png new file mode 100644 index 000000000..3dc20986f Binary files /dev/null and b/samples/browseable/WearDrawers/res/drawable-hdpi/ic_e_white_48dp.png differ diff --git a/samples/browseable/WearDrawers/res/drawable-hdpi/ic_info_outline_black_18dp.png b/samples/browseable/WearDrawers/res/drawable-hdpi/ic_info_outline_black_18dp.png new file mode 100644 index 000000000..05803bd15 Binary files /dev/null and b/samples/browseable/WearDrawers/res/drawable-hdpi/ic_info_outline_black_18dp.png differ diff --git a/samples/browseable/WearDrawers/res/drawable-hdpi/ic_j_white_48dp.png b/samples/browseable/WearDrawers/res/drawable-hdpi/ic_j_white_48dp.png new file mode 100644 index 000000000..03715893b Binary files /dev/null and b/samples/browseable/WearDrawers/res/drawable-hdpi/ic_j_white_48dp.png differ diff --git a/samples/browseable/WearDrawers/res/drawable-hdpi/ic_m_white_48dp.png b/samples/browseable/WearDrawers/res/drawable-hdpi/ic_m_white_48dp.png new file mode 100644 index 000000000..00b9858cc Binary files /dev/null and b/samples/browseable/WearDrawers/res/drawable-hdpi/ic_m_white_48dp.png differ diff --git a/samples/browseable/WearDrawers/res/drawable-hdpi/ic_n_white_48dp.png b/samples/browseable/WearDrawers/res/drawable-hdpi/ic_n_white_48dp.png new file mode 100644 index 000000000..33eceae3f Binary files /dev/null and b/samples/browseable/WearDrawers/res/drawable-hdpi/ic_n_white_48dp.png differ diff --git a/samples/browseable/WearDrawers/res/drawable-hdpi/ic_s_white_48dp.png b/samples/browseable/WearDrawers/res/drawable-hdpi/ic_s_white_48dp.png new file mode 100644 index 000000000..f767c8d91 Binary files /dev/null and b/samples/browseable/WearDrawers/res/drawable-hdpi/ic_s_white_48dp.png differ diff --git a/samples/browseable/WearDrawers/res/drawable-hdpi/ic_u_white_48dp.png b/samples/browseable/WearDrawers/res/drawable-hdpi/ic_u_white_48dp.png new file mode 100644 index 000000000..a3ba71b08 Binary files /dev/null and b/samples/browseable/WearDrawers/res/drawable-hdpi/ic_u_white_48dp.png differ diff --git a/samples/browseable/WearDrawers/res/drawable-hdpi/ic_v_white_48dp.png b/samples/browseable/WearDrawers/res/drawable-hdpi/ic_v_white_48dp.png new file mode 100644 index 000000000..ddfaffe19 Binary files /dev/null and b/samples/browseable/WearDrawers/res/drawable-hdpi/ic_v_white_48dp.png differ diff --git a/samples/browseable/WearDrawers/res/drawable-hdpi/jupiter.png b/samples/browseable/WearDrawers/res/drawable-hdpi/jupiter.png new file mode 100644 index 000000000..891502570 Binary files /dev/null and b/samples/browseable/WearDrawers/res/drawable-hdpi/jupiter.png differ diff --git a/samples/browseable/WearDrawers/res/drawable-hdpi/mars.png b/samples/browseable/WearDrawers/res/drawable-hdpi/mars.png new file mode 100644 index 000000000..ac7e4f836 Binary files /dev/null and b/samples/browseable/WearDrawers/res/drawable-hdpi/mars.png differ diff --git a/samples/browseable/WearDrawers/res/drawable-hdpi/mercury.png b/samples/browseable/WearDrawers/res/drawable-hdpi/mercury.png new file mode 100644 index 000000000..06329ca6d Binary files /dev/null and b/samples/browseable/WearDrawers/res/drawable-hdpi/mercury.png differ diff --git a/samples/browseable/WearDrawers/res/drawable-hdpi/neptune.png b/samples/browseable/WearDrawers/res/drawable-hdpi/neptune.png new file mode 100644 index 000000000..ce12b3724 Binary files /dev/null and b/samples/browseable/WearDrawers/res/drawable-hdpi/neptune.png differ diff --git a/samples/browseable/WearDrawers/res/drawable-hdpi/saturn.png b/samples/browseable/WearDrawers/res/drawable-hdpi/saturn.png new file mode 100644 index 000000000..9f51ca43e Binary files /dev/null and b/samples/browseable/WearDrawers/res/drawable-hdpi/saturn.png differ diff --git a/samples/browseable/WearDrawers/res/drawable-hdpi/uranus.png b/samples/browseable/WearDrawers/res/drawable-hdpi/uranus.png new file mode 100644 index 000000000..0cdf78812 Binary files /dev/null and b/samples/browseable/WearDrawers/res/drawable-hdpi/uranus.png differ diff --git a/samples/browseable/WearDrawers/res/drawable-hdpi/venus.png b/samples/browseable/WearDrawers/res/drawable-hdpi/venus.png new file mode 100644 index 000000000..68fee0ed6 Binary files /dev/null and b/samples/browseable/WearDrawers/res/drawable-hdpi/venus.png differ diff --git a/samples/browseable/WearDrawers/res/drawable-mdpi/ic_info_outline_black_18dp.png b/samples/browseable/WearDrawers/res/drawable-mdpi/ic_info_outline_black_18dp.png new file mode 100644 index 000000000..bb6095bd0 Binary files /dev/null and b/samples/browseable/WearDrawers/res/drawable-mdpi/ic_info_outline_black_18dp.png differ diff --git a/samples/browseable/WearDrawers/res/drawable-xhdpi/ic_info_outline_black_18dp.png b/samples/browseable/WearDrawers/res/drawable-xhdpi/ic_info_outline_black_18dp.png new file mode 100644 index 000000000..4b5ab06e1 Binary files /dev/null and b/samples/browseable/WearDrawers/res/drawable-xhdpi/ic_info_outline_black_18dp.png differ diff --git a/samples/browseable/WearDrawers/res/drawable-xxhdpi/ic_info_outline_black_18dp.png b/samples/browseable/WearDrawers/res/drawable-xxhdpi/ic_info_outline_black_18dp.png new file mode 100644 index 000000000..0b465fc75 Binary files /dev/null and b/samples/browseable/WearDrawers/res/drawable-xxhdpi/ic_info_outline_black_18dp.png differ diff --git a/samples/browseable/WearDrawers/res/drawable-xxxhdpi/ic_info_outline_black_18dp.png b/samples/browseable/WearDrawers/res/drawable-xxxhdpi/ic_info_outline_black_18dp.png new file mode 100644 index 000000000..3847a9fe7 Binary files /dev/null and b/samples/browseable/WearDrawers/res/drawable-xxxhdpi/ic_info_outline_black_18dp.png differ diff --git a/samples/browseable/WearDrawers/res/layout/fragment_planet.xml b/samples/browseable/WearDrawers/res/layout/fragment_planet.xml new file mode 100644 index 000000000..6409eb0ed --- /dev/null +++ b/samples/browseable/WearDrawers/res/layout/fragment_planet.xml @@ -0,0 +1,20 @@ + + \ No newline at end of file diff --git a/samples/browseable/WearDrawers/res/menu/action_drawer_menu.xml b/samples/browseable/WearDrawers/res/menu/action_drawer_menu.xml new file mode 100644 index 000000000..86060d2a4 --- /dev/null +++ b/samples/browseable/WearDrawers/res/menu/action_drawer_menu.xml @@ -0,0 +1,30 @@ + + + + + + + + \ No newline at end of file diff --git a/samples/browseable/WearDrawers/res/mipmap-hdpi/ic_launcher.png b/samples/browseable/WearDrawers/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..cde69bccc Binary files /dev/null and b/samples/browseable/WearDrawers/res/mipmap-hdpi/ic_launcher.png differ diff --git a/samples/browseable/WearDrawers/res/mipmap-mdpi/ic_launcher.png b/samples/browseable/WearDrawers/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..c133a0cbd Binary files /dev/null and b/samples/browseable/WearDrawers/res/mipmap-mdpi/ic_launcher.png differ diff --git a/samples/browseable/WearDrawers/res/mipmap-xhdpi/ic_launcher.png b/samples/browseable/WearDrawers/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..bfa42f0e7 Binary files /dev/null and b/samples/browseable/WearDrawers/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/samples/browseable/WearDrawers/res/mipmap-xxhdpi/ic_launcher.png b/samples/browseable/WearDrawers/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..324e72cdd Binary files /dev/null and b/samples/browseable/WearDrawers/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/samples/browseable/WearDrawers/res/values/strings.xml b/samples/browseable/WearDrawers/res/values/strings.xml new file mode 100644 index 000000000..0a0cd87b2 --- /dev/null +++ b/samples/browseable/WearDrawers/res/values/strings.xml @@ -0,0 +1,114 @@ + + + Wearable Drawer Layout + + + + mercury + venus + earth + mars + jupiter + saturn + uranus + neptune + + + + + Mercury + ic_m_white_48dp + mercury + 0 Moons + 0.056 x Earth + 0.147 x Earth + + + + Venus + ic_v_white_48dp + venus + 0 Moons + 0.857 x Earth + 0.902 x Earth + + + + Earth + ic_e_white_48dp + earth + 1 moon + 1,083,206,916,846 km3 + 510,064,472 km2 + + + + Mars + ic_m_white_48dp + mars + 2 Moons + 0.151 x Earth + 0.283 x Earth + + + + Jupiter + ic_j_white_48dp + jupiter + 67 Moons + 1,321.337 x Earth + 120.414 x Earth + + + + Saturn + ic_s_white_48dp + saturn + 62 moons + 763.594 x Earth + 83.543 x Earth + + + + Uranus + ic_u_white_48dp + uranus + 27 Moons + 63.085 x Earth + 15.847 x Earth + + + + Neptune + ic_n_white_48dp + neptune + 14 Moons + 57.723 x Earth + 14.980 x Earth + + \ No newline at end of file diff --git a/samples/browseable/WearDrawers/src/com.example.android.wearable.wear.weardrawers/Planet.java b/samples/browseable/WearDrawers/src/com.example.android.wearable.wear.weardrawers/Planet.java new file mode 100644 index 000000000..67960493c --- /dev/null +++ b/samples/browseable/WearDrawers/src/com.example.android.wearable.wear.weardrawers/Planet.java @@ -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; + } +} \ No newline at end of file diff --git a/samples/browseable/XYZTouristAttractions/Application/AndroidManifest.xml b/samples/browseable/XYZTouristAttractions/Application/AndroidManifest.xml index 9d88b3981..762f9cdae 100644 --- a/samples/browseable/XYZTouristAttractions/Application/AndroidManifest.xml +++ b/samples/browseable/XYZTouristAttractions/Application/AndroidManifest.xml @@ -55,7 +55,16 @@ - + + + + + + + + diff --git a/samples/browseable/XYZTouristAttractions/Wearable/AndroidManifest.xml b/samples/browseable/XYZTouristAttractions/Wearable/AndroidManifest.xml index d353b2906..fc086da1a 100644 --- a/samples/browseable/XYZTouristAttractions/Wearable/AndroidManifest.xml +++ b/samples/browseable/XYZTouristAttractions/Wearable/AndroidManifest.xml @@ -44,7 +44,14 @@ - + + + + + + + +