Update sample prebuilts for mnc-docs
Synced to //developers/samples/android commit 243feb49e8d1753b746f69ae5519eaace0e50605. Change-Id: I9255d2ad8f68669d77124b7840184171fb5a801b
@@ -250,6 +250,11 @@ public class CalendarQueryService extends IntentService
|
|||||||
public PutDataMapRequest toPutDataMapRequest(){
|
public PutDataMapRequest toPutDataMapRequest(){
|
||||||
final PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(
|
final PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(
|
||||||
makeDataItemPath(eventId, begin));
|
makeDataItemPath(eventId, begin));
|
||||||
|
/* In most cases (as in this one), you don't need your DataItem appear instantly. By
|
||||||
|
default, delivery of normal DataItems to the Wear network might be delayed in order to
|
||||||
|
improve battery life for user devices. However, if you can't tolerate a delay in the
|
||||||
|
sync of your DataItems, you can mark them as urgent via setUrgent().
|
||||||
|
*/
|
||||||
DataMap data = putDataMapRequest.getDataMap();
|
DataMap data = putDataMapRequest.getDataMap();
|
||||||
data.putString(DATA_ITEM_URI, putDataMapRequest.getUri().toString());
|
data.putString(DATA_ITEM_URI, putDataMapRequest.getUri().toString());
|
||||||
data.putLong(ID, id);
|
data.putLong(ID, id);
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ import android.os.Handler;
|
|||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.v13.app.FragmentCompat;
|
import android.support.v13.app.FragmentCompat;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Size;
|
import android.util.Size;
|
||||||
import android.util.SparseIntArray;
|
import android.util.SparseIntArray;
|
||||||
@@ -269,6 +270,11 @@ public class Camera2BasicFragment extends Fragment
|
|||||||
*/
|
*/
|
||||||
private Semaphore mCameraOpenCloseLock = new Semaphore(1);
|
private Semaphore mCameraOpenCloseLock = new Semaphore(1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the current camera device supports Flash or not.
|
||||||
|
*/
|
||||||
|
private boolean mFlashSupported;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link CameraCaptureSession.CaptureCallback} that handles events related to JPEG capture.
|
* A {@link CameraCaptureSession.CaptureCallback} that handles events related to JPEG capture.
|
||||||
*/
|
*/
|
||||||
@@ -568,6 +574,10 @@ public class Camera2BasicFragment extends Fragment
|
|||||||
mPreviewSize.getHeight(), mPreviewSize.getWidth());
|
mPreviewSize.getHeight(), mPreviewSize.getWidth());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the flash is supported.
|
||||||
|
Boolean available = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
|
||||||
|
mFlashSupported = available == null ? false : available;
|
||||||
|
|
||||||
mCameraId = cameraId;
|
mCameraId = cameraId;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -585,7 +595,7 @@ public class Camera2BasicFragment extends Fragment
|
|||||||
* Opens the camera specified by {@link Camera2BasicFragment#mCameraId}.
|
* Opens the camera specified by {@link Camera2BasicFragment#mCameraId}.
|
||||||
*/
|
*/
|
||||||
private void openCamera(int width, int height) {
|
private void openCamera(int width, int height) {
|
||||||
if (getActivity().checkSelfPermission(Manifest.permission.CAMERA)
|
if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA)
|
||||||
!= PackageManager.PERMISSION_GRANTED) {
|
!= PackageManager.PERMISSION_GRANTED) {
|
||||||
requestCameraPermission();
|
requestCameraPermission();
|
||||||
return;
|
return;
|
||||||
@@ -691,8 +701,7 @@ public class Camera2BasicFragment extends Fragment
|
|||||||
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
|
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
|
||||||
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
|
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
|
||||||
// Flash is automatically enabled when necessary.
|
// Flash is automatically enabled when necessary.
|
||||||
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
|
setAutoFlash(mPreviewRequestBuilder);
|
||||||
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
|
|
||||||
|
|
||||||
// Finally, we start displaying the camera preview.
|
// Finally, we start displaying the camera preview.
|
||||||
mPreviewRequest = mPreviewRequestBuilder.build();
|
mPreviewRequest = mPreviewRequestBuilder.build();
|
||||||
@@ -808,8 +817,7 @@ public class Camera2BasicFragment extends Fragment
|
|||||||
// Use the same AE and AF modes as the preview.
|
// Use the same AE and AF modes as the preview.
|
||||||
captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,
|
captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,
|
||||||
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
|
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
|
||||||
captureBuilder.set(CaptureRequest.CONTROL_AE_MODE,
|
setAutoFlash(captureBuilder);
|
||||||
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
|
|
||||||
|
|
||||||
// Orientation
|
// Orientation
|
||||||
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
|
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
|
||||||
@@ -844,8 +852,7 @@ public class Camera2BasicFragment extends Fragment
|
|||||||
// Reset the auto-focus trigger
|
// Reset the auto-focus trigger
|
||||||
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
|
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
|
||||||
CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
|
CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
|
||||||
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
|
setAutoFlash(mPreviewRequestBuilder);
|
||||||
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
|
|
||||||
mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
|
mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
|
||||||
mBackgroundHandler);
|
mBackgroundHandler);
|
||||||
// After this, the camera will go back to the normal state of preview.
|
// After this, the camera will go back to the normal state of preview.
|
||||||
@@ -877,6 +884,13 @@ public class Camera2BasicFragment extends Fragment
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setAutoFlash(CaptureRequest.Builder requestBuilder) {
|
||||||
|
if (mFlashSupported) {
|
||||||
|
requestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
|
||||||
|
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves a JPEG {@link Image} into the specified {@link File}.
|
* Saves a JPEG {@link Image} into the specified {@link File}.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -381,7 +381,9 @@ public class MainActivity extends Activity implements DataApi.DataListener,
|
|||||||
public void run() {
|
public void run() {
|
||||||
PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(COUNT_PATH);
|
PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(COUNT_PATH);
|
||||||
putDataMapRequest.getDataMap().putInt(COUNT_KEY, count++);
|
putDataMapRequest.getDataMap().putInt(COUNT_KEY, count++);
|
||||||
|
putDataMapRequest.setUrgent();
|
||||||
PutDataRequest request = putDataMapRequest.asPutDataRequest();
|
PutDataRequest request = putDataMapRequest.asPutDataRequest();
|
||||||
|
request.setUrgent();
|
||||||
|
|
||||||
LOGD(TAG, "Generating DataItem: " + request);
|
LOGD(TAG, "Generating DataItem: " + request);
|
||||||
if (!mGoogleApiClient.isConnected()) {
|
if (!mGoogleApiClient.isConnected()) {
|
||||||
@@ -442,6 +444,8 @@ public class MainActivity extends Activity implements DataApi.DataListener,
|
|||||||
dataMap.getDataMap().putAsset(IMAGE_KEY, asset);
|
dataMap.getDataMap().putAsset(IMAGE_KEY, asset);
|
||||||
dataMap.getDataMap().putLong("time", new Date().getTime());
|
dataMap.getDataMap().putLong("time", new Date().getTime());
|
||||||
PutDataRequest request = dataMap.asPutDataRequest();
|
PutDataRequest request = dataMap.asPutDataRequest();
|
||||||
|
request.setUrgent();
|
||||||
|
|
||||||
Wearable.DataApi.putDataItem(mGoogleApiClient, request)
|
Wearable.DataApi.putDataItem(mGoogleApiClient, request)
|
||||||
.setResultCallback(new ResultCallback<DataItemResult>() {
|
.setResultCallback(new ResultCallback<DataItemResult>() {
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
android:layout_height="fill_parent" >
|
android:layout_height="fill_parent" >
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
|
android:id="@+id/progressbar"
|
||||||
style="?android:attr/progressBarStyleLarge"
|
style="?android:attr/progressBarStyleLarge"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import android.view.View;
|
|||||||
import android.view.View.OnClickListener;
|
import android.view.View.OnClickListener;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
|
||||||
import com.example.android.displayingbitmaps.R;
|
import com.example.android.displayingbitmaps.R;
|
||||||
import com.example.android.displayingbitmaps.util.ImageFetcher;
|
import com.example.android.displayingbitmaps.util.ImageFetcher;
|
||||||
@@ -32,10 +33,11 @@ import com.example.android.displayingbitmaps.util.Utils;
|
|||||||
/**
|
/**
|
||||||
* This fragment will populate the children of the ViewPager from {@link ImageDetailActivity}.
|
* This fragment will populate the children of the ViewPager from {@link ImageDetailActivity}.
|
||||||
*/
|
*/
|
||||||
public class ImageDetailFragment extends Fragment {
|
public class ImageDetailFragment extends Fragment implements ImageWorker.OnImageLoadedListener {
|
||||||
private static final String IMAGE_DATA_EXTRA = "extra_image_data";
|
private static final String IMAGE_DATA_EXTRA = "extra_image_data";
|
||||||
private String mImageUrl;
|
private String mImageUrl;
|
||||||
private ImageView mImageView;
|
private ImageView mImageView;
|
||||||
|
private ProgressBar mProgressBar;
|
||||||
private ImageFetcher mImageFetcher;
|
private ImageFetcher mImageFetcher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -75,6 +77,7 @@ public class ImageDetailFragment extends Fragment {
|
|||||||
// Inflate and locate the main ImageView
|
// Inflate and locate the main ImageView
|
||||||
final View v = inflater.inflate(R.layout.image_detail_fragment, container, false);
|
final View v = inflater.inflate(R.layout.image_detail_fragment, container, false);
|
||||||
mImageView = (ImageView) v.findViewById(R.id.imageView);
|
mImageView = (ImageView) v.findViewById(R.id.imageView);
|
||||||
|
mProgressBar = (ProgressBar) v.findViewById(R.id.progressbar);
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +89,7 @@ public class ImageDetailFragment extends Fragment {
|
|||||||
// cache can be used over all pages in the ViewPager
|
// cache can be used over all pages in the ViewPager
|
||||||
if (ImageDetailActivity.class.isInstance(getActivity())) {
|
if (ImageDetailActivity.class.isInstance(getActivity())) {
|
||||||
mImageFetcher = ((ImageDetailActivity) getActivity()).getImageFetcher();
|
mImageFetcher = ((ImageDetailActivity) getActivity()).getImageFetcher();
|
||||||
mImageFetcher.loadImage(mImageUrl, mImageView);
|
mImageFetcher.loadImage(mImageUrl, mImageView, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass clicks on the ImageView to the parent activity to handle
|
// Pass clicks on the ImageView to the parent activity to handle
|
||||||
@@ -104,4 +107,11 @@ public class ImageDetailFragment extends Fragment {
|
|||||||
mImageView.setImageDrawable(null);
|
mImageView.setImageDrawable(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onImageLoaded(boolean success) {
|
||||||
|
// Set loading spinner to gone once image has loaded. Cloud also show
|
||||||
|
// an error view here if needed.
|
||||||
|
mProgressBar.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,8 +71,9 @@ public abstract class ImageWorker {
|
|||||||
*
|
*
|
||||||
* @param data The URL of the image to download.
|
* @param data The URL of the image to download.
|
||||||
* @param imageView The ImageView to bind the downloaded image to.
|
* @param imageView The ImageView to bind the downloaded image to.
|
||||||
|
* @param listener A listener that will be called back once the image has been loaded.
|
||||||
*/
|
*/
|
||||||
public void loadImage(Object data, ImageView imageView) {
|
public void loadImage(Object data, ImageView imageView, OnImageLoadedListener listener) {
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -86,9 +87,12 @@ public abstract class ImageWorker {
|
|||||||
if (value != null) {
|
if (value != null) {
|
||||||
// Bitmap found in memory cache
|
// Bitmap found in memory cache
|
||||||
imageView.setImageDrawable(value);
|
imageView.setImageDrawable(value);
|
||||||
|
if (listener != null) {
|
||||||
|
listener.onImageLoaded(true);
|
||||||
|
}
|
||||||
} else if (cancelPotentialWork(data, imageView)) {
|
} else if (cancelPotentialWork(data, imageView)) {
|
||||||
//BEGIN_INCLUDE(execute_background_task)
|
//BEGIN_INCLUDE(execute_background_task)
|
||||||
final BitmapWorkerTask task = new BitmapWorkerTask(data, imageView);
|
final BitmapWorkerTask task = new BitmapWorkerTask(data, imageView, listener);
|
||||||
final AsyncDrawable asyncDrawable =
|
final AsyncDrawable asyncDrawable =
|
||||||
new AsyncDrawable(mResources, mLoadingBitmap, task);
|
new AsyncDrawable(mResources, mLoadingBitmap, task);
|
||||||
imageView.setImageDrawable(asyncDrawable);
|
imageView.setImageDrawable(asyncDrawable);
|
||||||
@@ -101,6 +105,21 @@ public abstract class ImageWorker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load an image specified by the data parameter into an ImageView (override
|
||||||
|
* {@link ImageWorker#processBitmap(Object)} to define the processing logic). A memory and
|
||||||
|
* disk cache will be used if an {@link ImageCache} has been added using
|
||||||
|
* {@link ImageWorker#addImageCache(android.support.v4.app.FragmentManager, ImageCache.ImageCacheParams)}. If the
|
||||||
|
* image is found in the memory cache, it is set immediately, otherwise an {@link AsyncTask}
|
||||||
|
* will be created to asynchronously load the bitmap.
|
||||||
|
*
|
||||||
|
* @param data The URL of the image to download.
|
||||||
|
* @param imageView The ImageView to bind the downloaded image to.
|
||||||
|
*/
|
||||||
|
public void loadImage(Object data, ImageView imageView) {
|
||||||
|
loadImage(data, imageView, null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set placeholder bitmap that shows when the the background thread is running.
|
* Set placeholder bitmap that shows when the the background thread is running.
|
||||||
*
|
*
|
||||||
@@ -238,10 +257,18 @@ public abstract class ImageWorker {
|
|||||||
private class BitmapWorkerTask extends AsyncTask<Void, Void, BitmapDrawable> {
|
private class BitmapWorkerTask extends AsyncTask<Void, Void, BitmapDrawable> {
|
||||||
private Object mData;
|
private Object mData;
|
||||||
private final WeakReference<ImageView> imageViewReference;
|
private final WeakReference<ImageView> imageViewReference;
|
||||||
|
private final OnImageLoadedListener mOnImageLoadedListener;
|
||||||
|
|
||||||
public BitmapWorkerTask(Object data, ImageView imageView) {
|
public BitmapWorkerTask(Object data, ImageView imageView) {
|
||||||
mData = data;
|
mData = data;
|
||||||
imageViewReference = new WeakReference<ImageView>(imageView);
|
imageViewReference = new WeakReference<ImageView>(imageView);
|
||||||
|
mOnImageLoadedListener = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BitmapWorkerTask(Object data, ImageView imageView, OnImageLoadedListener listener) {
|
||||||
|
mData = data;
|
||||||
|
imageViewReference = new WeakReference<ImageView>(imageView);
|
||||||
|
mOnImageLoadedListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -318,6 +345,7 @@ public abstract class ImageWorker {
|
|||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(BitmapDrawable value) {
|
protected void onPostExecute(BitmapDrawable value) {
|
||||||
//BEGIN_INCLUDE(complete_background_work)
|
//BEGIN_INCLUDE(complete_background_work)
|
||||||
|
boolean success = false;
|
||||||
// if cancel was called on this task or the "exit early" flag is set then we're done
|
// if cancel was called on this task or the "exit early" flag is set then we're done
|
||||||
if (isCancelled() || mExitTasksEarly) {
|
if (isCancelled() || mExitTasksEarly) {
|
||||||
value = null;
|
value = null;
|
||||||
@@ -328,8 +356,12 @@ public abstract class ImageWorker {
|
|||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
Log.d(TAG, "onPostExecute - setting bitmap");
|
Log.d(TAG, "onPostExecute - setting bitmap");
|
||||||
}
|
}
|
||||||
|
success = true;
|
||||||
setImageDrawable(imageView, value);
|
setImageDrawable(imageView, value);
|
||||||
}
|
}
|
||||||
|
if (mOnImageLoadedListener != null) {
|
||||||
|
mOnImageLoadedListener.onImageLoaded(success);
|
||||||
|
}
|
||||||
//END_INCLUDE(complete_background_work)
|
//END_INCLUDE(complete_background_work)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,6 +389,19 @@ public abstract class ImageWorker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface definition for callback on image loaded successfully.
|
||||||
|
*/
|
||||||
|
public interface OnImageLoadedListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called once the image has been loaded.
|
||||||
|
* @param success True if the image was loaded successfully, false if
|
||||||
|
* there was an error.
|
||||||
|
*/
|
||||||
|
void onImageLoaded(boolean success);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A custom Drawable that will be attached to the imageView while the work is in progress.
|
* A custom Drawable that will be attached to the imageView while the work is in progress.
|
||||||
* Contains a reference to the actual worker task, so that it can be stopped if a new binding is
|
* Contains a reference to the actual worker task, so that it can be stopped if a new binding is
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ public class FindPhoneService extends IntentService implements GoogleApiClient.C
|
|||||||
// when it receives the change.
|
// when it receives the change.
|
||||||
PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(PATH_SOUND_ALARM);
|
PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(PATH_SOUND_ALARM);
|
||||||
putDataMapRequest.getDataMap().putBoolean(FIELD_ALARM_ON, alarmOn);
|
putDataMapRequest.getDataMap().putBoolean(FIELD_ALARM_ON, alarmOn);
|
||||||
|
putDataMapRequest.setUrgent();
|
||||||
Wearable.DataApi.putDataItem(mGoogleApiClient, putDataMapRequest.asPutDataRequest())
|
Wearable.DataApi.putDataItem(mGoogleApiClient, putDataMapRequest.asPutDataRequest())
|
||||||
.await();
|
.await();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ public class GeofenceTransitionsIntentService extends IntentService
|
|||||||
final PutDataMapRequest putDataMapRequest =
|
final PutDataMapRequest putDataMapRequest =
|
||||||
PutDataMapRequest.create(GEOFENCE_DATA_ITEM_PATH);
|
PutDataMapRequest.create(GEOFENCE_DATA_ITEM_PATH);
|
||||||
putDataMapRequest.getDataMap().putString(KEY_GEOFENCE_ID, triggeredGeoFenceId);
|
putDataMapRequest.getDataMap().putString(KEY_GEOFENCE_ID, triggeredGeoFenceId);
|
||||||
|
putDataMapRequest.setUrgent();
|
||||||
if (mGoogleApiClient.isConnected()) {
|
if (mGoogleApiClient.isConnected()) {
|
||||||
Wearable.DataApi.putDataItem(
|
Wearable.DataApi.putDataItem(
|
||||||
mGoogleApiClient, putDataMapRequest.asPutDataRequest()).await();
|
mGoogleApiClient, putDataMapRequest.asPutDataRequest()).await();
|
||||||
|
|||||||
@@ -248,7 +248,9 @@ public class MainActivity extends Activity implements DataApi.DataListener,
|
|||||||
dataMap.putInt(QUESTION_INDEX, questionIndex);
|
dataMap.putInt(QUESTION_INDEX, questionIndex);
|
||||||
dataMap.putStringArray(ANSWERS, answers);
|
dataMap.putStringArray(ANSWERS, answers);
|
||||||
dataMap.putInt(CORRECT_ANSWER_INDEX, correctAnswerIndex);
|
dataMap.putInt(CORRECT_ANSWER_INDEX, correctAnswerIndex);
|
||||||
return request.asPutDataRequest();
|
PutDataRequest putDataRequest = request.asPutDataRequest();
|
||||||
|
putDataRequest.setUrgent();
|
||||||
|
return putDataRequest;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -496,7 +498,10 @@ public class MainActivity extends Activity implements DataApi.DataListener,
|
|||||||
dataMap.putBoolean(QUESTION_WAS_DELETED, false);
|
dataMap.putBoolean(QUESTION_WAS_DELETED, false);
|
||||||
if (!mHasQuestionBeenAsked && dataMap.getInt(QUESTION_INDEX) == 0) {
|
if (!mHasQuestionBeenAsked && dataMap.getInt(QUESTION_INDEX) == 0) {
|
||||||
// Ask the first question now.
|
// Ask the first question now.
|
||||||
Wearable.DataApi.putDataItem(mGoogleApiClient, request.asPutDataRequest());
|
PutDataRequest putDataRequest = request.asPutDataRequest();
|
||||||
|
// Set to high priority in case it isn't already.
|
||||||
|
putDataRequest.setUrgent();
|
||||||
|
Wearable.DataApi.putDataItem(mGoogleApiClient, putDataRequest);
|
||||||
setHasQuestionBeenAsked(true);
|
setHasQuestionBeenAsked(true);
|
||||||
} else {
|
} else {
|
||||||
// Enqueue future questions.
|
// Enqueue future questions.
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ public class DeleteQuestionService extends IntentService
|
|||||||
DataMap dataMap = putDataMapRequest.getDataMap();
|
DataMap dataMap = putDataMapRequest.getDataMap();
|
||||||
dataMap.putBoolean(QUESTION_WAS_DELETED, true);
|
dataMap.putBoolean(QUESTION_WAS_DELETED, true);
|
||||||
PutDataRequest request = putDataMapRequest.asPutDataRequest();
|
PutDataRequest request = putDataMapRequest.asPutDataRequest();
|
||||||
|
request.setUrgent();
|
||||||
Wearable.DataApi.putDataItem(mGoogleApiClient, request).await();
|
Wearable.DataApi.putDataItem(mGoogleApiClient, request).await();
|
||||||
mGoogleApiClient.disconnect();
|
mGoogleApiClient.disconnect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ public class UpdateQuestionService extends IntentService
|
|||||||
dataMap.putBoolean(CHOSEN_ANSWER_CORRECT, chosenAnswerCorrect);
|
dataMap.putBoolean(CHOSEN_ANSWER_CORRECT, chosenAnswerCorrect);
|
||||||
dataMap.putBoolean(QUESTION_WAS_ANSWERED, true);
|
dataMap.putBoolean(QUESTION_WAS_ANSWERED, true);
|
||||||
PutDataRequest request = putDataMapRequest.asPutDataRequest();
|
PutDataRequest request = putDataMapRequest.asPutDataRequest();
|
||||||
|
request.setUrgent();
|
||||||
Wearable.DataApi.putDataItem(mGoogleApiClient, request).await();
|
Wearable.DataApi.putDataItem(mGoogleApiClient, request).await();
|
||||||
|
|
||||||
// Remove this question notification.
|
// Remove this question notification.
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.android.wearable.runtimepermissions"
|
||||||
|
android:versionCode="1"
|
||||||
|
android:versionName="1.0">
|
||||||
|
|
||||||
|
<uses-sdk
|
||||||
|
android:minSdkVersion="18"
|
||||||
|
android:targetSdkVersion="23" />
|
||||||
|
|
||||||
|
<!-- Permissions for phone. -->
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
|
<!-- Permissions for wearable:
|
||||||
|
Earlier watches require their permissions to be a subset of the phone apps permission in order
|
||||||
|
for the wear app to be installed. Therefore, you must include the permissions here as well as in
|
||||||
|
the wear manifest.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.BODY_SENSORS" />
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
|
||||||
|
<application android:allowBackup="true"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:theme="@style/Theme.AppCompat.Light">
|
||||||
|
<activity
|
||||||
|
android:name=".MainPhoneActivity"
|
||||||
|
android:label="@string/app_name" >
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".PhonePermissionRequestActivity"
|
||||||
|
android:label="@string/title_activity_phone_permission_request"
|
||||||
|
android:theme="@style/Theme.AppCompat.Light.NoActionBar" >
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".WearPermissionRequestActivity"
|
||||||
|
android:label="@string/title_activity_wear_permission_request"
|
||||||
|
android:theme="@style/Theme.AppCompat.Light.NoActionBar" >
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".IncomingRequestPhoneService"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="true" >
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
|
||||||
|
</manifest>
|
||||||
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 196 B |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 662 B |
|
After Width: | Height: | Size: 654 B |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 9.5 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
@@ -0,0 +1,58 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2015 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
tools:context=".MainActivity"
|
||||||
|
tools:deviceIds="wear_square"
|
||||||
|
android:padding="12dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/wearBodySensorsPermissionButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:drawableLeft="@drawable/ic_permission_denied"
|
||||||
|
android:text="@string/button_wear_label_activity_main"
|
||||||
|
android:onClick="onClickWearBodySensors" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/phoneStoragePermissionButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:drawableLeft="@drawable/ic_permission_denied"
|
||||||
|
android:text="@string/button_phone_label_activity_main"
|
||||||
|
android:onClick="onClickPhoneStorage" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/output"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="3"
|
||||||
|
android:text="@string/hello_phone_activity_main"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2015 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingTop="@dimen/activity_vertical_margin"
|
||||||
|
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||||
|
android:background="#4c9699">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:stateListAnimator="@null"
|
||||||
|
android:text="@string/no_thanks_activity_phone_permission_request"
|
||||||
|
android:id="@+id/deny_permission_request"
|
||||||
|
android:onClick="onClickDenyPermissionRequest"
|
||||||
|
android:textColor="#ffffff"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_alignParentStart="true" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:stateListAnimator="@null"
|
||||||
|
android:text="@string/continue_activity_phone_permission_request"
|
||||||
|
android:id="@+id/approve_permission_request"
|
||||||
|
android:onClick="onClickApprovePermissionRequest"
|
||||||
|
android:layout_alignTop="@+id/deny_permission_request"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="#ffffff" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
|
android:text="@string/main_message_activity_phone_permission_request"
|
||||||
|
android:id="@+id/mainMessageTextView"
|
||||||
|
android:textColor="#ffffff"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_below="@+id/imageView"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_marginTop="117dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:text="@string/details_message_activity_phone_permission_request"
|
||||||
|
android:id="@+id/detailsTextView"
|
||||||
|
android:textColor="#ffffff"
|
||||||
|
android:layout_below="@+id/mainMessageTextView"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_alignParentStart="true" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/imageView"
|
||||||
|
android:src="@drawable/ic_file_folder"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_marginTop="60dp" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2015 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingTop="@dimen/activity_vertical_margin"
|
||||||
|
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||||
|
android:background="#4c9699">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:stateListAnimator="@null"
|
||||||
|
android:text="@string/no_thanks_activity_wear_permission_request"
|
||||||
|
android:id="@+id/deny_permission_request"
|
||||||
|
android:onClick="onClickDenyPermissionRequest"
|
||||||
|
android:textColor="#ffffff"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_alignParentStart="true" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:stateListAnimator="@null"
|
||||||
|
android:text="@string/continue_activity_wear_permission_request"
|
||||||
|
android:id="@+id/approve_permission_request"
|
||||||
|
android:onClick="onClickApprovePermissionRequest"
|
||||||
|
android:layout_alignTop="@+id/deny_permission_request"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="#ffffff" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
|
android:text="@string/main_message_activity_wear_permission_request"
|
||||||
|
android:id="@+id/mainMessageTextView"
|
||||||
|
android:textColor="#ffffff"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_below="@+id/imageView"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_marginTop="117dp" />
|
||||||
|
<!--TODO: R.string.dialog_message_activity_main -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:text="@string/details_message_activity_wear_permission_request"
|
||||||
|
android:id="@+id/detailsTextView"
|
||||||
|
android:textColor="#ffffff"
|
||||||
|
android:layout_below="@+id/mainMessageTextView"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_alignParentStart="true" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/imageView"
|
||||||
|
android:src="@drawable/ic_hardware_watch"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_marginTop="60dp" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
@@ -0,0 +1,24 @@
|
|||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Semantic definitions -->
|
||||||
|
|
||||||
|
<dimen name="horizontal_page_margin">@dimen/margin_huge</dimen>
|
||||||
|
<dimen name="vertical_page_margin">@dimen/margin_medium</dimen>
|
||||||
|
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<style name="Widget.SampleMessage">
|
||||||
|
<item name="android:textAppearance">?android:textAppearanceLarge</item>
|
||||||
|
<item name="android:lineSpacingMultiplier">1.2</item>
|
||||||
|
<item name="android:shadowDy">-6.5</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Activity themes -->
|
||||||
|
<style name="Theme.Base" parent="android:Theme.Holo.Light" />
|
||||||
|
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Activity themes -->
|
||||||
|
<style name="Theme.Base" parent="android:Theme.Material.Light">
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2015 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
|
||||||
|
(such as screen margins) for screens with more than 820dp of available width. This
|
||||||
|
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
|
||||||
|
<dimen name="activity_horizontal_margin">64dp</dimen>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">RuntimePermissionsWear</string>
|
||||||
|
<string name="intro_message">
|
||||||
|
<![CDATA[
|
||||||
|
|
||||||
|
|
||||||
|
A sample that shows how you can handle remote data that requires permissions both on
|
||||||
|
a wearable device and a mobile device.
|
||||||
|
|
||||||
|
|
||||||
|
]]>
|
||||||
|
</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2015 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||||
|
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||||
|
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2015 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<string name="hello_phone_activity_main">Happy equals approved, sad equals denied.\n\nTo see results or request permissions, click on the buttons above.</string>
|
||||||
|
<string name="denied_permission_activity_main">You do not have the correct permissions. Tap sad face to bring up permission dialog again.</string>
|
||||||
|
<string name="button_wear_label_activity_main">Wear Sensors</string>
|
||||||
|
<string name="button_phone_label_activity_main">Phone Storage</string>
|
||||||
|
|
||||||
|
<string name="title_activity_phone_permission_request">PhonePermissionRequestActivity</string>
|
||||||
|
<string name="main_message_activity_phone_permission_request">See your directory structure by letting us read your phone\'s storage.</string>
|
||||||
|
<string name="details_message_activity_phone_permission_request">Your phone and watch experience need access to your phone\'s storage to show your top level directories.</string>
|
||||||
|
<string name="no_thanks_activity_phone_permission_request">No Thanks</string>
|
||||||
|
<string name="continue_activity_phone_permission_request">Continue</string>
|
||||||
|
|
||||||
|
<string name="title_activity_wear_permission_request">WearPermissionRequestActivity</string>
|
||||||
|
<string name="main_message_activity_wear_permission_request">See your total sensor count by letting us read your wear\'s sensors.</string>
|
||||||
|
<string name="details_message_activity_wear_permission_request">Your phone and watch experience need access to your wear\'s sensors to show sensor count.</string>
|
||||||
|
<string name="no_thanks_activity_wear_permission_request">No Thanks</string>
|
||||||
|
<string name="continue_activity_wear_permission_request">Open on Watch</string>
|
||||||
|
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Define standard dimensions to comply with Holo-style grids and rhythm. -->
|
||||||
|
|
||||||
|
<dimen name="margin_tiny">4dp</dimen>
|
||||||
|
<dimen name="margin_small">8dp</dimen>
|
||||||
|
<dimen name="margin_medium">16dp</dimen>
|
||||||
|
<dimen name="margin_large">32dp</dimen>
|
||||||
|
<dimen name="margin_huge">64dp</dimen>
|
||||||
|
|
||||||
|
<!-- Semantic definitions -->
|
||||||
|
|
||||||
|
<dimen name="horizontal_page_margin">@dimen/margin_medium</dimen>
|
||||||
|
<dimen name="vertical_page_margin">@dimen/margin_medium</dimen>
|
||||||
|
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Activity themes -->
|
||||||
|
|
||||||
|
<style name="Theme.Base" parent="android:Theme.Light" />
|
||||||
|
|
||||||
|
<style name="Theme.Sample" parent="Theme.Base" />
|
||||||
|
|
||||||
|
<style name="AppTheme" parent="Theme.Sample" />
|
||||||
|
<!-- Widget styling -->
|
||||||
|
|
||||||
|
<style name="Widget" />
|
||||||
|
|
||||||
|
<style name="Widget.SampleMessage">
|
||||||
|
<item name="android:textAppearance">?android:textAppearanceMedium</item>
|
||||||
|
<item name="android:lineSpacingMultiplier">1.1</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="Widget.SampleMessageTile">
|
||||||
|
<item name="android:background">@drawable/tile</item>
|
||||||
|
<item name="android:shadowColor">#7F000000</item>
|
||||||
|
<item name="android:shadowDy">-3.5</item>
|
||||||
|
<item name="android:shadowRadius">2</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2015 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<string-array name="android_wear_capabilities">
|
||||||
|
<item>phone_app_runtime_permissions</item>
|
||||||
|
</string-array>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 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.runtimepermissions;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.support.v4.app.ActivityCompat;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.example.android.wearable.runtimepermissions.common.Constants;
|
||||||
|
|
||||||
|
import com.google.android.gms.common.api.GoogleApiClient;
|
||||||
|
import com.google.android.gms.common.api.PendingResult;
|
||||||
|
import com.google.android.gms.wearable.DataMap;
|
||||||
|
import com.google.android.gms.wearable.MessageApi;
|
||||||
|
import com.google.android.gms.wearable.MessageEvent;
|
||||||
|
import com.google.android.gms.wearable.Wearable;
|
||||||
|
import com.google.android.gms.wearable.WearableListenerService;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles all incoming requests for phone data (and permissions) from wear devices.
|
||||||
|
*/
|
||||||
|
public class IncomingRequestPhoneService extends WearableListenerService {
|
||||||
|
|
||||||
|
private static final String TAG = "IncomingRequestService";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
Log.d(TAG, "onCreate()");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessageReceived(MessageEvent messageEvent) {
|
||||||
|
super.onMessageReceived(messageEvent);
|
||||||
|
Log.d(TAG, "onMessageReceived(): " + messageEvent);
|
||||||
|
|
||||||
|
String messagePath = messageEvent.getPath();
|
||||||
|
|
||||||
|
if (messagePath.equals(Constants.MESSAGE_PATH_PHONE)) {
|
||||||
|
|
||||||
|
DataMap dataMap = DataMap.fromByteArray(messageEvent.getData());
|
||||||
|
int requestType = dataMap.getInt(Constants.KEY_COMM_TYPE, 0);
|
||||||
|
|
||||||
|
if (requestType == Constants.COMM_TYPE_REQUEST_PROMPT_PERMISSION) {
|
||||||
|
promptUserForStoragePermission(messageEvent.getSourceNodeId());
|
||||||
|
|
||||||
|
} else if (requestType == Constants.COMM_TYPE_REQUEST_DATA) {
|
||||||
|
respondWithStorageInformation(messageEvent.getSourceNodeId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void promptUserForStoragePermission(String nodeId) {
|
||||||
|
boolean storagePermissionApproved =
|
||||||
|
ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||||
|
== PackageManager.PERMISSION_GRANTED;
|
||||||
|
|
||||||
|
if (storagePermissionApproved) {
|
||||||
|
DataMap dataMap = new DataMap();
|
||||||
|
dataMap.putInt(Constants.KEY_COMM_TYPE,
|
||||||
|
Constants.COMM_TYPE_RESPONSE_USER_APPROVED_PERMISSION);
|
||||||
|
sendMessage(nodeId, dataMap);
|
||||||
|
} else {
|
||||||
|
// Launch Phone Activity to grant storage permissions.
|
||||||
|
Intent startIntent = new Intent(this, PhonePermissionRequestActivity.class);
|
||||||
|
startIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
|
||||||
|
/* This extra is included to alert MainPhoneActivity to send back the permission
|
||||||
|
* results after the user has made their decision in PhonePermissionRequestActivity
|
||||||
|
* and it finishes.
|
||||||
|
*/
|
||||||
|
startIntent.putExtra(MainPhoneActivity.EXTRA_PROMPT_PERMISSION_FROM_WEAR, true);
|
||||||
|
startActivity(startIntent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void respondWithStorageInformation(String nodeId) {
|
||||||
|
|
||||||
|
boolean storagePermissionApproved =
|
||||||
|
ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||||
|
== PackageManager.PERMISSION_GRANTED;
|
||||||
|
|
||||||
|
if (!storagePermissionApproved) {
|
||||||
|
DataMap dataMap = new DataMap();
|
||||||
|
dataMap.putInt(Constants.KEY_COMM_TYPE,
|
||||||
|
Constants.COMM_TYPE_RESPONSE_PERMISSION_REQUIRED);
|
||||||
|
sendMessage(nodeId, dataMap);
|
||||||
|
} else {
|
||||||
|
/* To keep the sample simple, we are only displaying the top level list of directories.
|
||||||
|
* Otherwise, it will return a message that the media wasn't available.
|
||||||
|
*/
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
if (isExternalStorageReadable()) {
|
||||||
|
File externalStorageDirectory = Environment.getExternalStorageDirectory();
|
||||||
|
String[] fileList = externalStorageDirectory.list();
|
||||||
|
|
||||||
|
if (fileList.length > 0) {
|
||||||
|
stringBuilder.append("List of directories on phone:\n");
|
||||||
|
for (String file : fileList) {
|
||||||
|
stringBuilder.append(" - " + file + "\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stringBuilder.append("No files in external storage.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stringBuilder.append("No external media is available.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send valid results
|
||||||
|
DataMap dataMap = new DataMap();
|
||||||
|
dataMap.putInt(Constants.KEY_COMM_TYPE,
|
||||||
|
Constants.COMM_TYPE_RESPONSE_DATA);
|
||||||
|
dataMap.putString(Constants.KEY_PAYLOAD, stringBuilder.toString());
|
||||||
|
sendMessage(nodeId, dataMap);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendMessage(String nodeId, DataMap dataMap) {
|
||||||
|
Log.d(TAG, "sendMessage() Node: " + nodeId);
|
||||||
|
|
||||||
|
GoogleApiClient client = new GoogleApiClient.Builder(this)
|
||||||
|
.addApi(Wearable.API)
|
||||||
|
.build();
|
||||||
|
client.blockingConnect(Constants.CONNECTION_TIME_OUT_MS, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
|
||||||
|
PendingResult<MessageApi.SendMessageResult> pendingMessageResult =
|
||||||
|
Wearable.MessageApi.sendMessage(
|
||||||
|
client,
|
||||||
|
nodeId,
|
||||||
|
Constants.MESSAGE_PATH_WEAR,
|
||||||
|
dataMap.toByteArray());
|
||||||
|
|
||||||
|
MessageApi.SendMessageResult sendMessageResult =
|
||||||
|
pendingMessageResult.await(
|
||||||
|
Constants.CONNECTION_TIME_OUT_MS,
|
||||||
|
TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
if (!sendMessageResult.getStatus().isSuccess()) {
|
||||||
|
Log.d(TAG, "Sending message failed, status: "
|
||||||
|
+ sendMessageResult.getStatus());
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Message sent successfully");
|
||||||
|
}
|
||||||
|
client.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isExternalStorageReadable() {
|
||||||
|
String state = Environment.getExternalStorageState();
|
||||||
|
|
||||||
|
return Environment.MEDIA_MOUNTED.equals(state)
|
||||||
|
|| Environment.MEDIA_MOUNTED_READ_ONLY.equals(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,440 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 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.runtimepermissions;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.support.v4.app.ActivityCompat;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.example.android.wearable.runtimepermissions.common.Constants;
|
||||||
|
|
||||||
|
import com.google.android.gms.common.ConnectionResult;
|
||||||
|
import com.google.android.gms.common.api.GoogleApiClient;
|
||||||
|
import com.google.android.gms.common.api.PendingResult;
|
||||||
|
import com.google.android.gms.common.api.ResultCallback;
|
||||||
|
import com.google.android.gms.wearable.CapabilityApi;
|
||||||
|
import com.google.android.gms.wearable.CapabilityInfo;
|
||||||
|
import com.google.android.gms.wearable.DataMap;
|
||||||
|
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.Wearable;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays data that requires runtime permissions both locally (READ_EXTERNAL_STORAGE) and
|
||||||
|
* remotely on wear (BODY_SENSORS).
|
||||||
|
*
|
||||||
|
* The class also handles sending back the results of a permission request from a remote wear device
|
||||||
|
* when the permission has not been approved yet on the phone (uses EXTRA as trigger). In that case,
|
||||||
|
* the IncomingRequestPhoneService launches the splash Activity (PhonePermissionRequestActivity) to
|
||||||
|
* inform user of permission request. After the user decides what to do, it falls back to this
|
||||||
|
* Activity (which has all the GoogleApiClient code) to handle sending data across and keeps user
|
||||||
|
* in app experience.
|
||||||
|
*/
|
||||||
|
public class MainPhoneActivity extends AppCompatActivity implements
|
||||||
|
GoogleApiClient.ConnectionCallbacks,
|
||||||
|
GoogleApiClient.OnConnectionFailedListener,
|
||||||
|
CapabilityApi.CapabilityListener,
|
||||||
|
MessageApi.MessageListener,
|
||||||
|
ResultCallback<MessageApi.SendMessageResult> {
|
||||||
|
|
||||||
|
private static final String TAG = "MainPhoneActivity";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Alerts Activity that the initial request for permissions came from wear, and the Activity
|
||||||
|
* needs to send back the results (data or permission rejection).
|
||||||
|
*/
|
||||||
|
public static final String EXTRA_PROMPT_PERMISSION_FROM_WEAR =
|
||||||
|
"com.example.android.wearable.runtimepermissions.extra.PROMPT_PERMISSION_FROM_WEAR";
|
||||||
|
|
||||||
|
private static final int REQUEST_WEAR_PERMISSION_RATIONALE = 1;
|
||||||
|
|
||||||
|
private boolean mWearBodySensorsPermissionApproved;
|
||||||
|
private boolean mPhoneStoragePermissionApproved;
|
||||||
|
|
||||||
|
private boolean mWearRequestingPhoneStoragePermission;
|
||||||
|
|
||||||
|
private Button mWearBodySensorsPermissionButton;
|
||||||
|
private Button mPhoneStoragePermissionButton;
|
||||||
|
private TextView mOutputTextView;
|
||||||
|
|
||||||
|
private Set<Node> mWearNodeIds;
|
||||||
|
|
||||||
|
private GoogleApiClient mGoogleApiClient;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
Log.d(TAG, "onCreate()");
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Since this is a remote permission, we initialize it to false and then check the remote
|
||||||
|
* permission once the GoogleApiClient is connected.
|
||||||
|
*/
|
||||||
|
mWearBodySensorsPermissionApproved = false;
|
||||||
|
|
||||||
|
setContentView(R.layout.activity_main);
|
||||||
|
|
||||||
|
// Checks if wear app requested phone permission (permission request opens later if true).
|
||||||
|
mWearRequestingPhoneStoragePermission =
|
||||||
|
getIntent().getBooleanExtra(EXTRA_PROMPT_PERMISSION_FROM_WEAR, false);
|
||||||
|
|
||||||
|
mPhoneStoragePermissionButton =
|
||||||
|
(Button) findViewById(R.id.phoneStoragePermissionButton);
|
||||||
|
|
||||||
|
mWearBodySensorsPermissionButton =
|
||||||
|
(Button) findViewById(R.id.wearBodySensorsPermissionButton);
|
||||||
|
|
||||||
|
mOutputTextView = (TextView) findViewById(R.id.output);
|
||||||
|
|
||||||
|
mGoogleApiClient = new GoogleApiClient.Builder(this)
|
||||||
|
.addApi(Wearable.API)
|
||||||
|
.addConnectionCallbacks(this)
|
||||||
|
.addOnConnectionFailedListener(this)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onClickWearBodySensors(View view) {
|
||||||
|
|
||||||
|
logToUi("Requested info from wear device(s). New approval may be required.");
|
||||||
|
|
||||||
|
DataMap dataMap = new DataMap();
|
||||||
|
dataMap.putInt(Constants.KEY_COMM_TYPE, Constants.COMM_TYPE_REQUEST_DATA);
|
||||||
|
sendMessage(dataMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onClickPhoneStorage(View view) {
|
||||||
|
|
||||||
|
if (mPhoneStoragePermissionApproved) {
|
||||||
|
logToUi(getPhoneStorageInformation());
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// On 23+ (M+) devices, Storage permission not granted. Request permission.
|
||||||
|
Intent startIntent = new Intent(this, PhonePermissionRequestActivity.class);
|
||||||
|
startActivity(startIntent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
Log.d(TAG, "onPause()");
|
||||||
|
super.onPause();
|
||||||
|
if ((mGoogleApiClient != null) && (mGoogleApiClient.isConnected())) {
|
||||||
|
Wearable.CapabilityApi.removeCapabilityListener(
|
||||||
|
mGoogleApiClient,
|
||||||
|
this,
|
||||||
|
Constants.CAPABILITY_WEAR_APP);
|
||||||
|
Wearable.MessageApi.removeListener(mGoogleApiClient, this);
|
||||||
|
mGoogleApiClient.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
Log.d(TAG, "onResume()");
|
||||||
|
super.onResume();
|
||||||
|
|
||||||
|
/* Enables app to handle 23+ (M+) style permissions. It also covers user changing
|
||||||
|
* permission in settings and coming back to the app.
|
||||||
|
*/
|
||||||
|
mPhoneStoragePermissionApproved =
|
||||||
|
ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||||
|
== PackageManager.PERMISSION_GRANTED;
|
||||||
|
|
||||||
|
if (mPhoneStoragePermissionApproved) {
|
||||||
|
mPhoneStoragePermissionButton.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
|
R.drawable.ic_permission_approved, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mGoogleApiClient != null) {
|
||||||
|
mGoogleApiClient.connect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
Log.d(TAG, "onActivityResult()");
|
||||||
|
if (requestCode == REQUEST_WEAR_PERMISSION_RATIONALE) {
|
||||||
|
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
logToUi("Requested permission on wear device(s).");
|
||||||
|
|
||||||
|
DataMap dataMap = new DataMap();
|
||||||
|
dataMap.putInt(Constants.KEY_COMM_TYPE,
|
||||||
|
Constants.COMM_TYPE_REQUEST_PROMPT_PERMISSION);
|
||||||
|
sendMessage(dataMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnected(Bundle bundle) {
|
||||||
|
Log.d(TAG, "onConnected()");
|
||||||
|
|
||||||
|
// Set up listeners for capability and message changes.
|
||||||
|
Wearable.CapabilityApi.addCapabilityListener(
|
||||||
|
mGoogleApiClient,
|
||||||
|
this,
|
||||||
|
Constants.CAPABILITY_WEAR_APP);
|
||||||
|
Wearable.MessageApi.addListener(mGoogleApiClient, this);
|
||||||
|
|
||||||
|
// Initial check of capabilities to find the wear nodes.
|
||||||
|
PendingResult<CapabilityApi.GetCapabilityResult> pendingResult =
|
||||||
|
Wearable.CapabilityApi.getCapability(
|
||||||
|
mGoogleApiClient,
|
||||||
|
Constants.CAPABILITY_WEAR_APP,
|
||||||
|
CapabilityApi.FILTER_REACHABLE);
|
||||||
|
|
||||||
|
pendingResult.setResultCallback(new ResultCallback<CapabilityApi.GetCapabilityResult>() {
|
||||||
|
@Override
|
||||||
|
public void onResult(CapabilityApi.GetCapabilityResult getCapabilityResult) {
|
||||||
|
|
||||||
|
CapabilityInfo capabilityInfo = getCapabilityResult.getCapability();
|
||||||
|
String capabilityName = capabilityInfo.getName();
|
||||||
|
|
||||||
|
boolean wearSupportsSampleApp =
|
||||||
|
capabilityName.equals(Constants.CAPABILITY_WEAR_APP);
|
||||||
|
|
||||||
|
if (wearSupportsSampleApp) {
|
||||||
|
mWearNodeIds = capabilityInfo.getNodes();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Upon getting all wear nodes, we now need to check if the original request to
|
||||||
|
* launch this activity (and PhonePermissionRequestActivity) was initiated by
|
||||||
|
* a wear device. If it was, we need to send back the permission results (data
|
||||||
|
* or rejection of permission) to the wear device.
|
||||||
|
*
|
||||||
|
* Also, note we set variable to false, this enables the user to continue
|
||||||
|
* changing permissions without sending updates to the wear every time.
|
||||||
|
*/
|
||||||
|
if (mWearRequestingPhoneStoragePermission) {
|
||||||
|
mWearRequestingPhoneStoragePermission = false;
|
||||||
|
sendWearPermissionResults();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionSuspended(int i) {
|
||||||
|
Log.d(TAG, "onConnectionSuspended(): connection to location client suspended");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionFailed(ConnectionResult connectionResult) {
|
||||||
|
Log.e(TAG, "onConnectionFailed(): connection to location client failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void onCapabilityChanged(CapabilityInfo capabilityInfo) {
|
||||||
|
Log.d(TAG, "onCapabilityChanged(): " + capabilityInfo);
|
||||||
|
|
||||||
|
mWearNodeIds = capabilityInfo.getNodes();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onMessageReceived(MessageEvent messageEvent) {
|
||||||
|
Log.d(TAG, "onMessageReceived(): " + messageEvent);
|
||||||
|
|
||||||
|
String messagePath = messageEvent.getPath();
|
||||||
|
|
||||||
|
if (messagePath.equals(Constants.MESSAGE_PATH_PHONE)) {
|
||||||
|
DataMap dataMap = DataMap.fromByteArray(messageEvent.getData());
|
||||||
|
|
||||||
|
int commType = dataMap.getInt(Constants.KEY_COMM_TYPE, 0);
|
||||||
|
|
||||||
|
if (commType == Constants.COMM_TYPE_RESPONSE_PERMISSION_REQUIRED) {
|
||||||
|
mWearBodySensorsPermissionApproved = false;
|
||||||
|
updateWearButtonOnUiThread();
|
||||||
|
|
||||||
|
/* Because our request for remote data requires a remote permission, we now launch
|
||||||
|
* a splash activity informing the user we need those permissions (along with
|
||||||
|
* other helpful information to approve).
|
||||||
|
*/
|
||||||
|
Intent wearPermissionRationale =
|
||||||
|
new Intent(this, WearPermissionRequestActivity.class);
|
||||||
|
startActivityForResult(wearPermissionRationale, REQUEST_WEAR_PERMISSION_RATIONALE);
|
||||||
|
|
||||||
|
} else if (commType == Constants.COMM_TYPE_RESPONSE_USER_APPROVED_PERMISSION) {
|
||||||
|
mWearBodySensorsPermissionApproved = true;
|
||||||
|
updateWearButtonOnUiThread();
|
||||||
|
logToUi("User approved permission on remote device, requesting data again.");
|
||||||
|
DataMap outgoingDataRequestDataMap = new DataMap();
|
||||||
|
outgoingDataRequestDataMap.putInt(Constants.KEY_COMM_TYPE,
|
||||||
|
Constants.COMM_TYPE_REQUEST_DATA);
|
||||||
|
sendMessage(outgoingDataRequestDataMap);
|
||||||
|
|
||||||
|
} else if (commType == Constants.COMM_TYPE_RESPONSE_USER_DENIED_PERMISSION) {
|
||||||
|
mWearBodySensorsPermissionApproved = false;
|
||||||
|
updateWearButtonOnUiThread();
|
||||||
|
logToUi("User denied permission on remote device.");
|
||||||
|
|
||||||
|
} else if (commType == Constants.COMM_TYPE_RESPONSE_DATA) {
|
||||||
|
mWearBodySensorsPermissionApproved = true;
|
||||||
|
String storageDetails = dataMap.getString(Constants.KEY_PAYLOAD);
|
||||||
|
updateWearButtonOnUiThread();
|
||||||
|
logToUi(storageDetails);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Unrecognized communication type received.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResult(MessageApi.SendMessageResult sendMessageResult) {
|
||||||
|
if (!sendMessageResult.getStatus().isSuccess()) {
|
||||||
|
Log.d(TAG, "Sending message failed, onResult: " + sendMessageResult);
|
||||||
|
updateWearButtonOnUiThread();
|
||||||
|
logToUi("Sending message failed.");
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Message sent.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendMessage(DataMap dataMap) {
|
||||||
|
Log.d(TAG, "sendMessage(): " + mWearNodeIds);
|
||||||
|
|
||||||
|
if ((mWearNodeIds != null) && (!mWearNodeIds.isEmpty())) {
|
||||||
|
|
||||||
|
PendingResult<MessageApi.SendMessageResult> pendingResult;
|
||||||
|
|
||||||
|
for (Node node : mWearNodeIds) {
|
||||||
|
|
||||||
|
pendingResult = Wearable.MessageApi.sendMessage(
|
||||||
|
mGoogleApiClient,
|
||||||
|
node.getId(),
|
||||||
|
Constants.MESSAGE_PATH_WEAR,
|
||||||
|
dataMap.toByteArray());
|
||||||
|
|
||||||
|
pendingResult.setResultCallback(this, Constants.CONNECTION_TIME_OUT_MS,
|
||||||
|
TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Unable to retrieve node with proper capability
|
||||||
|
mWearBodySensorsPermissionApproved = false;
|
||||||
|
updateWearButtonOnUiThread();
|
||||||
|
logToUi("Wear devices not available to send message.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateWearButtonOnUiThread() {
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (mWearBodySensorsPermissionApproved) {
|
||||||
|
mWearBodySensorsPermissionButton.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
|
R.drawable.ic_permission_approved, 0, 0, 0);
|
||||||
|
} else {
|
||||||
|
mWearBodySensorsPermissionButton.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
|
R.drawable.ic_permission_denied, 0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Handles all messages for the UI coming on and off the main thread. Not all callbacks happen
|
||||||
|
* on the main thread.
|
||||||
|
*/
|
||||||
|
private void logToUi(final String message) {
|
||||||
|
|
||||||
|
boolean mainUiThread = (Looper.myLooper() == Looper.getMainLooper());
|
||||||
|
|
||||||
|
if (mainUiThread) {
|
||||||
|
|
||||||
|
if (!message.isEmpty()) {
|
||||||
|
Log.d(TAG, message);
|
||||||
|
mOutputTextView.setText(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (!message.isEmpty()) {
|
||||||
|
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
Log.d(TAG, message);
|
||||||
|
mOutputTextView.setText(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getPhoneStorageInformation() {
|
||||||
|
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
String state = Environment.getExternalStorageState();
|
||||||
|
boolean isExternalStorageReadable = Environment.MEDIA_MOUNTED.equals(state)
|
||||||
|
|| Environment.MEDIA_MOUNTED_READ_ONLY.equals(state);
|
||||||
|
|
||||||
|
if (isExternalStorageReadable) {
|
||||||
|
File externalStorageDirectory = Environment.getExternalStorageDirectory();
|
||||||
|
String[] fileList = externalStorageDirectory.list();
|
||||||
|
|
||||||
|
if (fileList.length > 0) {
|
||||||
|
|
||||||
|
stringBuilder.append("List of files\n");
|
||||||
|
for (String file : fileList) {
|
||||||
|
stringBuilder.append(" - " + file + "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
stringBuilder.append("No files in external storage.");
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
stringBuilder.append("No external media is available.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return stringBuilder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendWearPermissionResults() {
|
||||||
|
|
||||||
|
Log.d(TAG, "sendWearPermissionResults()");
|
||||||
|
|
||||||
|
DataMap dataMap = new DataMap();
|
||||||
|
|
||||||
|
if (mPhoneStoragePermissionApproved) {
|
||||||
|
dataMap.putInt(Constants.KEY_COMM_TYPE,
|
||||||
|
Constants.COMM_TYPE_RESPONSE_USER_APPROVED_PERMISSION);
|
||||||
|
} else {
|
||||||
|
dataMap.putInt(Constants.KEY_COMM_TYPE,
|
||||||
|
Constants.COMM_TYPE_RESPONSE_USER_DENIED_PERMISSION);
|
||||||
|
}
|
||||||
|
sendMessage(dataMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 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.runtimepermissions;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.v4.app.ActivityCompat;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a simple splash screen (activity) for giving more details on why the user should approve
|
||||||
|
* phone permissions for storage. If they choose to move forward, the permission screen
|
||||||
|
* is brought up. Either way (approve or disapprove), this will exit to the MainPhoneActivity after
|
||||||
|
* they are finished with their final decision.
|
||||||
|
*
|
||||||
|
* If this activity is started by our service (IncomingRequestPhoneService) it is marked via an
|
||||||
|
* extra (MainPhoneActivity.EXTRA_PROMPT_PERMISSION_FROM_WEAR). That service only starts
|
||||||
|
* this activity if the phone permission hasn't been approved for the data wear is trying to access.
|
||||||
|
* When the user decides within this Activity what to do with the permission request, it closes and
|
||||||
|
* opens the MainPhoneActivity (to maintain the app experience). It also again passes along the same
|
||||||
|
* extra (MainPhoneActivity.EXTRA_PROMPT_PERMISSION_FROM_WEAR) to alert MainPhoneActivity to
|
||||||
|
* send the results of the user's decision to the wear device.
|
||||||
|
*/
|
||||||
|
public class PhonePermissionRequestActivity extends AppCompatActivity implements
|
||||||
|
ActivityCompat.OnRequestPermissionsResultCallback {
|
||||||
|
|
||||||
|
private static final String TAG = "PhoneRationale";
|
||||||
|
|
||||||
|
/* Id to identify Location permission request. */
|
||||||
|
private static final int PERMISSION_REQUEST_READ_STORAGE = 1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
// If permissions granted, we start the main activity (shut this activity down).
|
||||||
|
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||||
|
== PackageManager.PERMISSION_GRANTED) {
|
||||||
|
startMainActivity();
|
||||||
|
}
|
||||||
|
|
||||||
|
setContentView(R.layout.activity_phone_permission_request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onClickApprovePermissionRequest(View view) {
|
||||||
|
Log.d(TAG, "onClickApprovePermissionRequest()");
|
||||||
|
|
||||||
|
// On 23+ (M+) devices, External storage permission not granted. Request permission.
|
||||||
|
ActivityCompat.requestPermissions(
|
||||||
|
this,
|
||||||
|
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
|
||||||
|
PERMISSION_REQUEST_READ_STORAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onClickDenyPermissionRequest(View view) {
|
||||||
|
Log.d(TAG, "onClickDenyPermissionRequest()");
|
||||||
|
startMainActivity();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Callback received when a permissions request has been completed.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(
|
||||||
|
int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||||
|
|
||||||
|
String permissionResult = "Request code: " + requestCode + ", Permissions: " + permissions
|
||||||
|
+ ", Results: " + grantResults;
|
||||||
|
Log.d(TAG, "onRequestPermissionsResult(): " + permissionResult);
|
||||||
|
|
||||||
|
if (requestCode == PERMISSION_REQUEST_READ_STORAGE) {
|
||||||
|
// Close activity regardless of user's decision (decision picked up in main activity).
|
||||||
|
startMainActivity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startMainActivity() {
|
||||||
|
|
||||||
|
Intent mainActivityIntent = new Intent(this, MainPhoneActivity.class);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If service started this Activity (b/c wear requested data where permissions were not
|
||||||
|
* approved), tells MainPhoneActivity to send results to wear device (via this extra).
|
||||||
|
*/
|
||||||
|
boolean serviceStartedActivity = getIntent().getBooleanExtra(
|
||||||
|
MainPhoneActivity.EXTRA_PROMPT_PERMISSION_FROM_WEAR, false);
|
||||||
|
|
||||||
|
if (serviceStartedActivity) {
|
||||||
|
mainActivityIntent.putExtra(
|
||||||
|
MainPhoneActivity.EXTRA_PROMPT_PERMISSION_FROM_WEAR, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
startActivity(mainActivityIntent);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 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.runtimepermissions;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.ActivityCompat;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a simple splash screen (activity) for giving more details on why the user should approve
|
||||||
|
* phone permissions for storage. If they choose to move forward, the permission screen
|
||||||
|
* is brought up. Either way (approve or disapprove), this will exit to the MainPhoneActivity after
|
||||||
|
* they are finished with their final decision.
|
||||||
|
*
|
||||||
|
* If this activity is started by our service (IncomingRequestPhoneService) it is marked via an
|
||||||
|
* extra (MainPhoneActivity.EXTRA_PROMPT_PERMISSION_FROM_WEAR). That service only starts
|
||||||
|
* this activity if the phone permission hasn't been approved for the data wear is trying to access.
|
||||||
|
* When the user decides within this Activity what to do with the permission request, it closes and
|
||||||
|
* opens the MainPhoneActivity (to maintain the app experience). It also again passes along the same
|
||||||
|
* extra (MainPhoneActivity.EXTRA_PROMPT_PERMISSION_FROM_WEAR) to alert MainPhoneActivity to
|
||||||
|
* send the results of the user's decision to the wear device.
|
||||||
|
*/
|
||||||
|
public class WearPermissionRequestActivity extends AppCompatActivity implements
|
||||||
|
ActivityCompat.OnRequestPermissionsResultCallback {
|
||||||
|
|
||||||
|
private static final String TAG = "WearRationale";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_wear_permission_request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onClickApprovePermissionRequest(View view) {
|
||||||
|
Log.d(TAG, "onClickApprovePermissionRequest()");
|
||||||
|
setResult(Activity.RESULT_OK);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onClickDenyPermissionRequest(View view) {
|
||||||
|
Log.d(TAG, "onClickDenyPermissionRequest()");
|
||||||
|
setResult(Activity.RESULT_CANCELED);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2015 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.android.wearable.runtimepermissions.common">
|
||||||
|
|
||||||
|
<application android:allowBackup="true" android:label="@string/app_name">
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2015 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">Shared</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 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.runtimepermissions.common;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A collection of constants that is shared between the wearable and handset apps.
|
||||||
|
*/
|
||||||
|
public class Constants {
|
||||||
|
|
||||||
|
// Shared
|
||||||
|
public static final long CONNECTION_TIME_OUT_MS = TimeUnit.SECONDS.toMillis(5);
|
||||||
|
|
||||||
|
public static final String KEY_COMM_TYPE = "communicationType";
|
||||||
|
public static final String KEY_PAYLOAD = "payload";
|
||||||
|
|
||||||
|
// Requests
|
||||||
|
public static final int COMM_TYPE_REQUEST_PROMPT_PERMISSION = 1;
|
||||||
|
public static final int COMM_TYPE_REQUEST_DATA = 2;
|
||||||
|
|
||||||
|
// Responses
|
||||||
|
public static final int COMM_TYPE_RESPONSE_PERMISSION_REQUIRED = 1001;
|
||||||
|
public static final int COMM_TYPE_RESPONSE_USER_APPROVED_PERMISSION = 1002;
|
||||||
|
public static final int COMM_TYPE_RESPONSE_USER_DENIED_PERMISSION = 1003;
|
||||||
|
public static final int COMM_TYPE_RESPONSE_DATA = 1004;
|
||||||
|
|
||||||
|
// Phone
|
||||||
|
public static final String CAPABILITY_PHONE_APP = "phone_app_runtime_permissions";
|
||||||
|
public static final String MESSAGE_PATH_PHONE = "/phone_message_path";
|
||||||
|
|
||||||
|
// Wear
|
||||||
|
public static final String CAPABILITY_WEAR_APP = "wear_app_runtime_permissions";
|
||||||
|
public static final String MESSAGE_PATH_WEAR = "/wear_message_path";
|
||||||
|
|
||||||
|
private Constants() {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2015 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.android.wearable.runtimepermissions" >
|
||||||
|
|
||||||
|
<uses-feature android:name="android.hardware.type.watch" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
<uses-permission android:name="android.permission.BODY_SENSORS" />
|
||||||
|
|
||||||
|
<uses-sdk
|
||||||
|
android:minSdkVersion="21"
|
||||||
|
android:targetSdkVersion="23" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:theme="@android:style/Theme.DeviceDefault" >
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.android.gms.version"
|
||||||
|
android:value="@integer/google_play_services_version" />
|
||||||
|
|
||||||
|
<!-- If you want your app to run on pre-22, then set required to false -->
|
||||||
|
<uses-library
|
||||||
|
android:name="com.google.android.wearable"
|
||||||
|
android:required="false" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".MainWearActivity"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:launchMode="singleInstance">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".RequestPermissionOnPhoneActivity"
|
||||||
|
android:label="@string/title_activity_request_permission_on_phone"
|
||||||
|
android:theme="@android:style/Theme.DeviceDefault.Light">
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".IncomingRequestWearService"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="true" >
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
|
After Width: | Height: | Size: 843 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 679 B |
|
After Width: | Height: | Size: 662 B |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 654 B |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 429 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2015 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<android.support.wearable.view.WatchViewStub
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/watch_view_stub"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:rectLayout="@layout/rect_activity_main"
|
||||||
|
app:roundLayout="@layout/round_activity_main"
|
||||||
|
tools:context=".MainActivity"
|
||||||
|
tools:deviceIds="wear">
|
||||||
|
</android.support.wearable.view.WatchViewStub>
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2015 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<android.support.wearable.view.BoxInsetLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:deviceIds="wear"
|
||||||
|
android:background="@color/white"
|
||||||
|
tools:context="com.example.android.wearable.runtimepermissions.RequestPermissionOnPhoneActivity"
|
||||||
|
android:paddingStart="30dp"
|
||||||
|
android:paddingTop="18dp"
|
||||||
|
android:paddingRight="18dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:onClick="onClickPermissionPhoneStorage"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_box="all">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/permission_message_activity_request_permission_on_phone"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingRight="6dp"
|
||||||
|
android:textColor="#000000"/>
|
||||||
|
|
||||||
|
<android.support.v4.widget.Space
|
||||||
|
android:layout_width="18dp"
|
||||||
|
android:layout_height="18dp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<android.support.wearable.view.CircledImageView
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
app:circle_radius="20dp"
|
||||||
|
app:circle_color="#0086D4"
|
||||||
|
android:src="@drawable/ic_cc_open_on_phone"/>
|
||||||
|
|
||||||
|
<android.support.v4.widget.Space
|
||||||
|
android:layout_width="8dp"
|
||||||
|
android:layout_height="8dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/openOnPhone"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:text="@string/open_on_phone_message_activity_request_permission_on_phone"
|
||||||
|
android:textColor="#0086D4"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</android.support.wearable.view.BoxInsetLayout>
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2015 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
tools:context=".MainActivity"
|
||||||
|
tools:deviceIds="wear_square">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/wearBodySensorsPermissionButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:drawableLeft="@drawable/ic_permission_denied"
|
||||||
|
android:textSize="8sp"
|
||||||
|
android:text="@string/button_wear_label_activity_main"
|
||||||
|
android:onClick="onClickWearBodySensors" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/phoneStoragePermissionButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:drawableLeft="@drawable/ic_permission_denied"
|
||||||
|
android:textSize="8sp"
|
||||||
|
android:text="@string/button_phone_label_activity_main"
|
||||||
|
android:onClick="onClickPhoneStorage" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/output"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="2"
|
||||||
|
android:paddingLeft="8dp"
|
||||||
|
android:paddingRight="8dp"
|
||||||
|
android:text="@string/hello_wear_activity_main" />
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2015 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
tools:context=".MainActivity"
|
||||||
|
tools:deviceIds="wear_round"
|
||||||
|
android:paddingTop="24dp"
|
||||||
|
android:paddingLeft="10dp"
|
||||||
|
android:paddingRight="10dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/wearBodySensorsPermissionButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:drawableLeft="@drawable/ic_permission_denied"
|
||||||
|
android:textSize="8sp"
|
||||||
|
android:text="@string/button_wear_label_activity_main"
|
||||||
|
android:onClick="onClickWearBodySensors" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/phoneStoragePermissionButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:drawableLeft="@drawable/ic_permission_denied"
|
||||||
|
android:textSize="8sp"
|
||||||
|
android:text="@string/button_phone_label_activity_main"
|
||||||
|
android:onClick="onClickPhoneStorage" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/output"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="2"
|
||||||
|
android:paddingLeft="8dp"
|
||||||
|
android:paddingRight="8dp"
|
||||||
|
android:text="@string/hello_wear_activity_main" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2014 The Android Open Source Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<dimen name="pair_button_diameter">40dp</dimen>
|
||||||
|
<dimen name="circle_border_normal_width">10dp</dimen>
|
||||||
|
<dimen name="circle_padding">5dp</dimen>
|
||||||
|
<dimen name="circle_radius">35dp</dimen>
|
||||||
|
<dimen name="circle_radius_pressed">40dp</dimen>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2015 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">Runtime Permissions</string>
|
||||||
|
|
||||||
|
<string name="hello_wear_activity_main">Happy equals approved, sad equals denied.\n\nTo see results or request permissions, click on the buttons above.</string>
|
||||||
|
<string name="button_wear_label_activity_main">Wear Sensors</string>
|
||||||
|
<string name="button_phone_label_activity_main">Phone Storage</string>
|
||||||
|
|
||||||
|
<string name="title_activity_request_permission_on_phone">PhonePermissionRationale</string>
|
||||||
|
<string name="permission_message_activity_request_permission_on_phone">App requires access to your phone\'s storage.</string>
|
||||||
|
<string name="open_on_phone_message_activity_request_permission_on_phone">Open on phone</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2015 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<string-array name="android_wear_capabilities">
|
||||||
|
<item>wear_app_runtime_permissions</item>
|
||||||
|
</string-array>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 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.runtimepermissions;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.hardware.Sensor;
|
||||||
|
import android.hardware.SensorManager;
|
||||||
|
import android.support.v4.app.ActivityCompat;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.example.android.wearable.runtimepermissions.common.Constants;
|
||||||
|
|
||||||
|
import com.google.android.gms.common.ConnectionResult;
|
||||||
|
import com.google.android.gms.common.api.GoogleApiClient;
|
||||||
|
import com.google.android.gms.common.api.PendingResult;
|
||||||
|
import com.google.android.gms.wearable.CapabilityApi;
|
||||||
|
import com.google.android.gms.wearable.CapabilityInfo;
|
||||||
|
import com.google.android.gms.wearable.DataMap;
|
||||||
|
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.Wearable;
|
||||||
|
import com.google.android.gms.wearable.WearableListenerService;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles all incoming requests for wear data (and permissions) from phone devices.
|
||||||
|
*/
|
||||||
|
public class IncomingRequestWearService extends WearableListenerService {
|
||||||
|
|
||||||
|
private static final String TAG = "IncomingRequestService";
|
||||||
|
|
||||||
|
public IncomingRequestWearService() {
|
||||||
|
Log.d(TAG, "IncomingRequestWearService()");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
Log.d(TAG, "onCreate()");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessageReceived(MessageEvent messageEvent) {
|
||||||
|
Log.d(TAG, "onMessageReceived(): " + messageEvent);
|
||||||
|
|
||||||
|
String messagePath = messageEvent.getPath();
|
||||||
|
|
||||||
|
if (messagePath.equals(Constants.MESSAGE_PATH_WEAR)) {
|
||||||
|
DataMap dataMap = DataMap.fromByteArray(messageEvent.getData());
|
||||||
|
|
||||||
|
int requestType = dataMap.getInt(Constants.KEY_COMM_TYPE);
|
||||||
|
|
||||||
|
if (requestType == Constants.COMM_TYPE_REQUEST_PROMPT_PERMISSION) {
|
||||||
|
promptUserForSensorPermission();
|
||||||
|
|
||||||
|
} else if (requestType == Constants.COMM_TYPE_REQUEST_DATA) {
|
||||||
|
respondWithSensorInformation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void promptUserForSensorPermission() {
|
||||||
|
Log.d(TAG, "promptUserForSensorPermission()");
|
||||||
|
|
||||||
|
boolean sensorPermissionApproved =
|
||||||
|
ActivityCompat.checkSelfPermission(this, Manifest.permission.BODY_SENSORS)
|
||||||
|
== PackageManager.PERMISSION_GRANTED;
|
||||||
|
|
||||||
|
if (sensorPermissionApproved) {
|
||||||
|
DataMap dataMap = new DataMap();
|
||||||
|
dataMap.putInt(Constants.KEY_COMM_TYPE,
|
||||||
|
Constants.COMM_TYPE_RESPONSE_USER_APPROVED_PERMISSION);
|
||||||
|
sendMessage(dataMap);
|
||||||
|
} else {
|
||||||
|
// Launch Activity to grant sensor permissions.
|
||||||
|
Intent startIntent = new Intent(this, MainWearActivity.class);
|
||||||
|
startIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
startIntent.putExtra(MainWearActivity.EXTRA_PROMPT_PERMISSION_FROM_PHONE, true);
|
||||||
|
startActivity(startIntent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void respondWithSensorInformation() {
|
||||||
|
Log.d(TAG, "respondWithSensorInformation()");
|
||||||
|
|
||||||
|
boolean sensorPermissionApproved =
|
||||||
|
ActivityCompat.checkSelfPermission(this, Manifest.permission.BODY_SENSORS)
|
||||||
|
== PackageManager.PERMISSION_GRANTED;
|
||||||
|
|
||||||
|
if (!sensorPermissionApproved) {
|
||||||
|
DataMap dataMap = new DataMap();
|
||||||
|
dataMap.putInt(Constants.KEY_COMM_TYPE,
|
||||||
|
Constants.COMM_TYPE_RESPONSE_PERMISSION_REQUIRED);
|
||||||
|
sendMessage(dataMap);
|
||||||
|
} else {
|
||||||
|
/* To keep the sample simple, we are only displaying the number of sensors. You could do
|
||||||
|
* something much more complicated.
|
||||||
|
*/
|
||||||
|
SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
|
||||||
|
List<Sensor> sensorList = sensorManager.getSensorList(Sensor.TYPE_ALL);
|
||||||
|
int numberOfSensorsOnDevice = sensorList.size();
|
||||||
|
|
||||||
|
String sensorSummary = numberOfSensorsOnDevice + " sensors on wear device(s)!";
|
||||||
|
DataMap dataMap = new DataMap();
|
||||||
|
dataMap.putInt(Constants.KEY_COMM_TYPE,
|
||||||
|
Constants.COMM_TYPE_RESPONSE_DATA);
|
||||||
|
dataMap.putString(Constants.KEY_PAYLOAD, sensorSummary);
|
||||||
|
sendMessage(dataMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendMessage(DataMap dataMap) {
|
||||||
|
|
||||||
|
Log.d(TAG, "sendMessage(): " + dataMap);
|
||||||
|
|
||||||
|
GoogleApiClient googleApiClient = new GoogleApiClient.Builder(this)
|
||||||
|
.addApi(Wearable.API)
|
||||||
|
.build();
|
||||||
|
ConnectionResult connectionResult =
|
||||||
|
googleApiClient.blockingConnect(
|
||||||
|
Constants.CONNECTION_TIME_OUT_MS,
|
||||||
|
TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
if (!connectionResult.isSuccess()) {
|
||||||
|
Log.d(TAG, "Google API Client failed to connect.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PendingResult<CapabilityApi.GetCapabilityResult> pendingCapabilityResult =
|
||||||
|
Wearable.CapabilityApi.getCapability(
|
||||||
|
googleApiClient,
|
||||||
|
Constants.CAPABILITY_PHONE_APP,
|
||||||
|
CapabilityApi.FILTER_REACHABLE);
|
||||||
|
|
||||||
|
CapabilityApi.GetCapabilityResult getCapabilityResult =
|
||||||
|
pendingCapabilityResult.await(
|
||||||
|
Constants.CONNECTION_TIME_OUT_MS,
|
||||||
|
TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
if (!getCapabilityResult.getStatus().isSuccess()) {
|
||||||
|
Log.d(TAG, "CapabilityApi failed to return any results.");
|
||||||
|
googleApiClient.disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CapabilityInfo capabilityInfo = getCapabilityResult.getCapability();
|
||||||
|
String phoneNodeId = pickBestNodeId(capabilityInfo.getNodes());
|
||||||
|
|
||||||
|
PendingResult<MessageApi.SendMessageResult> pendingMessageResult =
|
||||||
|
Wearable.MessageApi.sendMessage(
|
||||||
|
googleApiClient,
|
||||||
|
phoneNodeId,
|
||||||
|
Constants.MESSAGE_PATH_PHONE,
|
||||||
|
dataMap.toByteArray());
|
||||||
|
|
||||||
|
MessageApi.SendMessageResult sendMessageResult =
|
||||||
|
pendingMessageResult.await(Constants.CONNECTION_TIME_OUT_MS, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
if (!sendMessageResult.getStatus().isSuccess()) {
|
||||||
|
Log.d(TAG, "Sending message failed, onResult: " + sendMessageResult.getStatus());
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Message sent successfully");
|
||||||
|
}
|
||||||
|
|
||||||
|
googleApiClient.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* There should only ever be one phone in a node set (much less w/ the correct capability), so
|
||||||
|
* I am just grabbing the first one (which should be the only one).
|
||||||
|
*/
|
||||||
|
private String pickBestNodeId(Set<Node> nodes) {
|
||||||
|
|
||||||
|
Log.d(TAG, "pickBestNodeId: " + nodes);
|
||||||
|
|
||||||
|
|
||||||
|
String bestNodeId = null;
|
||||||
|
/* Find a nearby node or pick one arbitrarily. There should be only one phone connected
|
||||||
|
* that supports this sample.
|
||||||
|
*/
|
||||||
|
for (Node node : nodes) {
|
||||||
|
if (node.isNearby()) {
|
||||||
|
return node.getId();
|
||||||
|
}
|
||||||
|
bestNodeId = node.getId();
|
||||||
|
}
|
||||||
|
return bestNodeId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,556 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 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.runtimepermissions;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.hardware.Sensor;
|
||||||
|
import android.hardware.SensorManager;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.v4.app.ActivityCompat;
|
||||||
|
import android.support.wearable.activity.WearableActivity;
|
||||||
|
import android.support.wearable.view.WatchViewStub;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.example.android.wearable.runtimepermissions.common.Constants;
|
||||||
|
|
||||||
|
import com.google.android.gms.common.ConnectionResult;
|
||||||
|
import com.google.android.gms.common.api.GoogleApiClient;
|
||||||
|
import com.google.android.gms.common.api.PendingResult;
|
||||||
|
import com.google.android.gms.common.api.ResultCallback;
|
||||||
|
import com.google.android.gms.wearable.CapabilityApi;
|
||||||
|
import com.google.android.gms.wearable.CapabilityInfo;
|
||||||
|
import com.google.android.gms.wearable.DataMap;
|
||||||
|
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.Wearable;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays data that requires runtime permissions both locally (BODY_SENSORS) and remotely on
|
||||||
|
* the phone (READ_EXTERNAL_STORAGE).
|
||||||
|
*
|
||||||
|
* The class is also launched by IncomingRequestWearService when the permission for the data the
|
||||||
|
* phone is trying to access hasn't been granted (wear's sensors). If granted in that scenario,
|
||||||
|
* this Activity also sends back the results of the permission request to the phone device (and
|
||||||
|
* the sensor data if approved).
|
||||||
|
*/
|
||||||
|
public class MainWearActivity extends WearableActivity implements
|
||||||
|
GoogleApiClient.ConnectionCallbacks,
|
||||||
|
GoogleApiClient.OnConnectionFailedListener,
|
||||||
|
CapabilityApi.CapabilityListener,
|
||||||
|
MessageApi.MessageListener,
|
||||||
|
ActivityCompat.OnRequestPermissionsResultCallback {
|
||||||
|
|
||||||
|
private static final String TAG = "MainWearActivity";
|
||||||
|
|
||||||
|
/* Id to identify local permission request for body sensors. */
|
||||||
|
private static final int PERMISSION_REQUEST_READ_BODY_SENSORS = 1;
|
||||||
|
|
||||||
|
/* Id to identify starting/closing RequestPermissionOnPhoneActivity (startActivityForResult). */
|
||||||
|
private static final int REQUEST_PHONE_PERMISSION = 1;
|
||||||
|
|
||||||
|
public static final String EXTRA_PROMPT_PERMISSION_FROM_PHONE =
|
||||||
|
"com.example.android.wearable.runtimepermissions.extra.PROMPT_PERMISSION_FROM_PHONE";
|
||||||
|
|
||||||
|
private boolean mWearBodySensorsPermissionApproved;
|
||||||
|
private boolean mPhoneStoragePermissionApproved;
|
||||||
|
|
||||||
|
private boolean mPhoneRequestingWearSensorPermission;
|
||||||
|
|
||||||
|
private Button mWearBodySensorsPermissionButton;
|
||||||
|
private Button mPhoneStoragePermissionButton;
|
||||||
|
private TextView mOutputTextView;
|
||||||
|
|
||||||
|
private String mPhoneNodeId;
|
||||||
|
|
||||||
|
private GoogleApiClient mGoogleApiClient;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
Log.d(TAG, "onCreate()");
|
||||||
|
super.onCreate(savedInstanceState);;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Since this is a remote permission, we initialize it to false and then check the remote
|
||||||
|
* permission once the GoogleApiClient is connected.
|
||||||
|
*/
|
||||||
|
mPhoneStoragePermissionApproved = false;
|
||||||
|
|
||||||
|
setContentView(R.layout.activity_main);
|
||||||
|
setAmbientEnabled();
|
||||||
|
|
||||||
|
// Checks if phone app requested wear permission (permission request opens later if true).
|
||||||
|
mPhoneRequestingWearSensorPermission =
|
||||||
|
getIntent().getBooleanExtra(EXTRA_PROMPT_PERMISSION_FROM_PHONE, false);
|
||||||
|
|
||||||
|
final WatchViewStub stub = (WatchViewStub) findViewById(R.id.watch_view_stub);
|
||||||
|
stub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener() {
|
||||||
|
@Override
|
||||||
|
public void onLayoutInflated(WatchViewStub stub) {
|
||||||
|
|
||||||
|
mWearBodySensorsPermissionButton =
|
||||||
|
(Button) stub.findViewById(R.id.wearBodySensorsPermissionButton);
|
||||||
|
|
||||||
|
if (mWearBodySensorsPermissionApproved) {
|
||||||
|
mWearBodySensorsPermissionButton.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
|
R.drawable.ic_permission_approved, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
mPhoneStoragePermissionButton =
|
||||||
|
(Button) stub.findViewById(R.id.phoneStoragePermissionButton);
|
||||||
|
|
||||||
|
mOutputTextView = (TextView) stub.findViewById(R.id.output);
|
||||||
|
|
||||||
|
if (mPhoneRequestingWearSensorPermission) {
|
||||||
|
launchPermissionDialogForPhone();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mGoogleApiClient = new GoogleApiClient.Builder(this)
|
||||||
|
.addApi(Wearable.API)
|
||||||
|
.addConnectionCallbacks(this)
|
||||||
|
.addOnConnectionFailedListener(this)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onClickWearBodySensors(View view) {
|
||||||
|
|
||||||
|
if (mWearBodySensorsPermissionApproved) {
|
||||||
|
|
||||||
|
// To keep the sample simple, we are only displaying the number of sensors.
|
||||||
|
SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
|
||||||
|
List<Sensor> sensorList = sensorManager.getSensorList(Sensor.TYPE_ALL);
|
||||||
|
int numberOfSensorsOnDevice = sensorList.size();
|
||||||
|
|
||||||
|
logToUi(numberOfSensorsOnDevice + " sensors on device(s)!");
|
||||||
|
|
||||||
|
} else {
|
||||||
|
logToUi("Requested local permission.");
|
||||||
|
// On 23+ (M+) devices, GPS permission not granted. Request permission.
|
||||||
|
ActivityCompat.requestPermissions(
|
||||||
|
this,
|
||||||
|
new String[]{Manifest.permission.BODY_SENSORS},
|
||||||
|
PERMISSION_REQUEST_READ_BODY_SENSORS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onClickPhoneStorage(View view) {
|
||||||
|
|
||||||
|
logToUi("Requested info from phone. New approval may be required.");
|
||||||
|
DataMap dataMap = new DataMap();
|
||||||
|
dataMap.putInt(Constants.KEY_COMM_TYPE,
|
||||||
|
Constants.COMM_TYPE_REQUEST_DATA);
|
||||||
|
sendMessage(dataMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
Log.d(TAG, "onPause()");
|
||||||
|
super.onPause();
|
||||||
|
if ((mGoogleApiClient != null) && mGoogleApiClient.isConnected()) {
|
||||||
|
Wearable.CapabilityApi.removeCapabilityListener(
|
||||||
|
mGoogleApiClient,
|
||||||
|
this,
|
||||||
|
Constants.CAPABILITY_PHONE_APP);
|
||||||
|
Wearable.MessageApi.removeListener(mGoogleApiClient, this);
|
||||||
|
mGoogleApiClient.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
Log.d(TAG, "onResume()");
|
||||||
|
super.onResume();
|
||||||
|
if (mGoogleApiClient != null) {
|
||||||
|
mGoogleApiClient.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enables app to handle 23+ (M+) style permissions.
|
||||||
|
mWearBodySensorsPermissionApproved =
|
||||||
|
ActivityCompat.checkSelfPermission(this, Manifest.permission.BODY_SENSORS)
|
||||||
|
== PackageManager.PERMISSION_GRANTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Because this wear activity is marked "android:launchMode='singleInstance'" in the manifest,
|
||||||
|
* we need to allow the permissions dialog to be opened up from the phone even if the wear app
|
||||||
|
* is in the foreground. By overriding onNewIntent, we can cover that use case.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void onNewIntent (Intent intent) {
|
||||||
|
Log.d(TAG, "onNewIntent()");
|
||||||
|
super.onNewIntent(intent);
|
||||||
|
|
||||||
|
// Checks if phone app requested wear permissions (opens up permission request if true).
|
||||||
|
mPhoneRequestingWearSensorPermission =
|
||||||
|
intent.getBooleanExtra(EXTRA_PROMPT_PERMISSION_FROM_PHONE, false);
|
||||||
|
|
||||||
|
if (mPhoneRequestingWearSensorPermission) {
|
||||||
|
launchPermissionDialogForPhone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnterAmbient(Bundle ambientDetails) {
|
||||||
|
Log.d(TAG, "onEnterAmbient() " + ambientDetails);
|
||||||
|
|
||||||
|
if (mWearBodySensorsPermissionApproved) {
|
||||||
|
mWearBodySensorsPermissionButton.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
|
R.drawable.ic_permission_approved_bw, 0, 0, 0);
|
||||||
|
} else {
|
||||||
|
mWearBodySensorsPermissionButton.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
|
R.drawable.ic_permission_denied_bw, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mPhoneStoragePermissionApproved) {
|
||||||
|
mPhoneStoragePermissionButton.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
|
R.drawable.ic_permission_approved_bw, 0, 0, 0);
|
||||||
|
} else {
|
||||||
|
mPhoneStoragePermissionButton.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
|
R.drawable.ic_permission_denied_bw, 0, 0, 0);
|
||||||
|
}
|
||||||
|
super.onEnterAmbient(ambientDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onExitAmbient() {
|
||||||
|
Log.d(TAG, "onExitAmbient()");
|
||||||
|
|
||||||
|
if (mWearBodySensorsPermissionApproved) {
|
||||||
|
mWearBodySensorsPermissionButton.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
|
R.drawable.ic_permission_approved, 0, 0, 0);
|
||||||
|
} else {
|
||||||
|
mWearBodySensorsPermissionButton.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
|
R.drawable.ic_permission_denied, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mPhoneStoragePermissionApproved) {
|
||||||
|
mPhoneStoragePermissionButton.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
|
R.drawable.ic_permission_approved, 0, 0, 0);
|
||||||
|
} else {
|
||||||
|
mPhoneStoragePermissionButton.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
|
R.drawable.ic_permission_denied, 0, 0, 0);
|
||||||
|
}
|
||||||
|
super.onExitAmbient();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnected(Bundle bundle) {
|
||||||
|
Log.d(TAG, "onConnected()");
|
||||||
|
|
||||||
|
// Set up listeners for capability and message changes.
|
||||||
|
Wearable.CapabilityApi.addCapabilityListener(
|
||||||
|
mGoogleApiClient,
|
||||||
|
this,
|
||||||
|
Constants.CAPABILITY_PHONE_APP);
|
||||||
|
Wearable.MessageApi.addListener(mGoogleApiClient, this);
|
||||||
|
|
||||||
|
// Initial check of capabilities to find the phone.
|
||||||
|
PendingResult<CapabilityApi.GetCapabilityResult> pendingResult =
|
||||||
|
Wearable.CapabilityApi.getCapability(
|
||||||
|
mGoogleApiClient,
|
||||||
|
Constants.CAPABILITY_PHONE_APP,
|
||||||
|
CapabilityApi.FILTER_REACHABLE);
|
||||||
|
|
||||||
|
pendingResult.setResultCallback(new ResultCallback<CapabilityApi.GetCapabilityResult>() {
|
||||||
|
@Override
|
||||||
|
public void onResult(CapabilityApi.GetCapabilityResult getCapabilityResult) {
|
||||||
|
|
||||||
|
if (getCapabilityResult.getStatus().isSuccess()) {
|
||||||
|
CapabilityInfo capabilityInfo = getCapabilityResult.getCapability();
|
||||||
|
mPhoneNodeId = pickBestNodeId(capabilityInfo.getNodes());
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Failed CapabilityApi result: "
|
||||||
|
+ getCapabilityResult.getStatus());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionSuspended(int i) {
|
||||||
|
Log.d(TAG, "onConnectionSuspended(): connection to location client suspended");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionFailed(ConnectionResult connectionResult) {
|
||||||
|
Log.e(TAG, "onConnectionFailed(): connection to location client failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onCapabilityChanged(CapabilityInfo capabilityInfo) {
|
||||||
|
Log.d(TAG, "onCapabilityChanged(): " + capabilityInfo);
|
||||||
|
|
||||||
|
mPhoneNodeId = pickBestNodeId(capabilityInfo.getNodes());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Callback received when a permissions request has been completed.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(
|
||||||
|
int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||||
|
|
||||||
|
String permissionResult = "Request code: " + requestCode + ", Permissions: " + permissions
|
||||||
|
+ ", Results: " + grantResults;
|
||||||
|
Log.d(TAG, "onRequestPermissionsResult(): " + permissionResult);
|
||||||
|
|
||||||
|
|
||||||
|
if (requestCode == PERMISSION_REQUEST_READ_BODY_SENSORS) {
|
||||||
|
|
||||||
|
if ((grantResults.length == 1)
|
||||||
|
&& (grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
|
||||||
|
|
||||||
|
mWearBodySensorsPermissionApproved = true;
|
||||||
|
mWearBodySensorsPermissionButton.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
|
R.drawable.ic_permission_approved, 0, 0, 0);
|
||||||
|
|
||||||
|
// To keep the sample simple, we are only displaying the number of sensors.
|
||||||
|
SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
|
||||||
|
List<Sensor> sensorList = sensorManager.getSensorList(Sensor.TYPE_ALL);
|
||||||
|
int numberOfSensorsOnDevice = sensorList.size();
|
||||||
|
|
||||||
|
String sensorSummary = numberOfSensorsOnDevice + " sensors on this device!";
|
||||||
|
logToUi(sensorSummary);
|
||||||
|
|
||||||
|
if (mPhoneRequestingWearSensorPermission) {
|
||||||
|
// Resets so this isn't triggered every time permission is changed in app.
|
||||||
|
mPhoneRequestingWearSensorPermission = false;
|
||||||
|
|
||||||
|
// Send 'approved' message to remote phone since it started Activity.
|
||||||
|
DataMap dataMap = new DataMap();
|
||||||
|
dataMap.putInt(Constants.KEY_COMM_TYPE,
|
||||||
|
Constants.COMM_TYPE_RESPONSE_USER_APPROVED_PERMISSION);
|
||||||
|
sendMessage(dataMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
mWearBodySensorsPermissionApproved = false;
|
||||||
|
mWearBodySensorsPermissionButton.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
|
R.drawable.ic_permission_denied, 0, 0, 0);
|
||||||
|
|
||||||
|
if (mPhoneRequestingWearSensorPermission) {
|
||||||
|
// Resets so this isn't triggered every time permission is changed in app.
|
||||||
|
mPhoneRequestingWearSensorPermission = false;
|
||||||
|
// Send 'denied' message to remote phone since it started Activity.
|
||||||
|
DataMap dataMap = new DataMap();
|
||||||
|
dataMap.putInt(Constants.KEY_COMM_TYPE,
|
||||||
|
Constants.COMM_TYPE_RESPONSE_USER_DENIED_PERMISSION);
|
||||||
|
sendMessage(dataMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onMessageReceived(MessageEvent messageEvent) {
|
||||||
|
Log.d(TAG, "onMessageReceived(): " + messageEvent);
|
||||||
|
|
||||||
|
String messagePath = messageEvent.getPath();
|
||||||
|
|
||||||
|
if (messagePath.equals(Constants.MESSAGE_PATH_WEAR)) {
|
||||||
|
|
||||||
|
DataMap dataMap = DataMap.fromByteArray(messageEvent.getData());
|
||||||
|
int commType = dataMap.getInt(Constants.KEY_COMM_TYPE, 0);
|
||||||
|
|
||||||
|
if (commType == Constants.COMM_TYPE_RESPONSE_PERMISSION_REQUIRED) {
|
||||||
|
mPhoneStoragePermissionApproved = false;
|
||||||
|
updatePhoneButtonOnUiThread();
|
||||||
|
|
||||||
|
/* Because our request for remote data requires a remote permission, we now launch
|
||||||
|
* a splash activity informing the user we need those permissions (along with
|
||||||
|
* other helpful information to approve).
|
||||||
|
*/
|
||||||
|
Intent phonePermissionRationaleIntent =
|
||||||
|
new Intent(this, RequestPermissionOnPhoneActivity.class);
|
||||||
|
startActivityForResult(phonePermissionRationaleIntent, REQUEST_PHONE_PERMISSION);
|
||||||
|
|
||||||
|
} else if (commType == Constants.COMM_TYPE_RESPONSE_USER_APPROVED_PERMISSION) {
|
||||||
|
mPhoneStoragePermissionApproved = true;
|
||||||
|
updatePhoneButtonOnUiThread();
|
||||||
|
logToUi("User approved permission on remote device, requesting data again.");
|
||||||
|
DataMap outgoingDataRequestDataMap = new DataMap();
|
||||||
|
outgoingDataRequestDataMap.putInt(Constants.KEY_COMM_TYPE,
|
||||||
|
Constants.COMM_TYPE_REQUEST_DATA);
|
||||||
|
sendMessage(outgoingDataRequestDataMap);
|
||||||
|
|
||||||
|
} else if (commType == Constants.COMM_TYPE_RESPONSE_USER_DENIED_PERMISSION) {
|
||||||
|
mPhoneStoragePermissionApproved = false;
|
||||||
|
updatePhoneButtonOnUiThread();
|
||||||
|
logToUi("User denied permission on remote device.");
|
||||||
|
|
||||||
|
} else if (commType == Constants.COMM_TYPE_RESPONSE_DATA) {
|
||||||
|
mPhoneStoragePermissionApproved = true;
|
||||||
|
String storageDetails = dataMap.getString(Constants.KEY_PAYLOAD);
|
||||||
|
updatePhoneButtonOnUiThread();
|
||||||
|
logToUi(storageDetails);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendMessage(DataMap dataMap) {
|
||||||
|
Log.d(TAG, "sendMessage(): " + mPhoneNodeId);
|
||||||
|
|
||||||
|
if (mPhoneNodeId != null) {
|
||||||
|
|
||||||
|
PendingResult<MessageApi.SendMessageResult> pendingResult =
|
||||||
|
Wearable.MessageApi.sendMessage(
|
||||||
|
mGoogleApiClient,
|
||||||
|
mPhoneNodeId,
|
||||||
|
Constants.MESSAGE_PATH_PHONE,
|
||||||
|
dataMap.toByteArray());
|
||||||
|
|
||||||
|
pendingResult.setResultCallback(new ResultCallback<MessageApi.SendMessageResult>() {
|
||||||
|
@Override
|
||||||
|
public void onResult(MessageApi.SendMessageResult sendMessageResult) {
|
||||||
|
|
||||||
|
if (!sendMessageResult.getStatus().isSuccess()) {
|
||||||
|
updatePhoneButtonOnUiThread();
|
||||||
|
logToUi("Sending message failed.");
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Message sent successfully.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, Constants.CONNECTION_TIME_OUT_MS, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Unable to retrieve node with proper capability
|
||||||
|
mPhoneStoragePermissionApproved = false;
|
||||||
|
updatePhoneButtonOnUiThread();
|
||||||
|
logToUi("Phone not available to send message.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
// Check which request we're responding to
|
||||||
|
if (requestCode == REQUEST_PHONE_PERMISSION) {
|
||||||
|
// Make sure the request was successful
|
||||||
|
if (resultCode == RESULT_OK) {
|
||||||
|
logToUi("Requested permission on phone.");
|
||||||
|
DataMap dataMap = new DataMap();
|
||||||
|
dataMap.putInt(Constants.KEY_COMM_TYPE,
|
||||||
|
Constants.COMM_TYPE_REQUEST_PROMPT_PERMISSION);
|
||||||
|
sendMessage(dataMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* There should only ever be one phone in a node set (much less w/ the correct capability), so
|
||||||
|
* I am just grabbing the first one (which should be the only one).
|
||||||
|
*/
|
||||||
|
private String pickBestNodeId(Set<Node> nodes) {
|
||||||
|
|
||||||
|
String bestNodeId = null;
|
||||||
|
// Find a nearby node or pick one arbitrarily.
|
||||||
|
for (Node node : nodes) {
|
||||||
|
if (node.isNearby()) {
|
||||||
|
return node.getId();
|
||||||
|
}
|
||||||
|
bestNodeId = node.getId();
|
||||||
|
}
|
||||||
|
return bestNodeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If Phone triggered the wear app for permissions, we open up the permission
|
||||||
|
* dialog after inflation.
|
||||||
|
*/
|
||||||
|
private void launchPermissionDialogForPhone() {
|
||||||
|
Log.d(TAG, "launchPermissionDialogForPhone()");
|
||||||
|
|
||||||
|
if (!mWearBodySensorsPermissionApproved) {
|
||||||
|
// On 23+ (M+) devices, GPS permission not granted. Request permission.
|
||||||
|
ActivityCompat.requestPermissions(
|
||||||
|
MainWearActivity.this,
|
||||||
|
new String[]{Manifest.permission.BODY_SENSORS},
|
||||||
|
PERMISSION_REQUEST_READ_BODY_SENSORS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePhoneButtonOnUiThread() {
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
if (mPhoneStoragePermissionApproved) {
|
||||||
|
|
||||||
|
if (isAmbient()) {
|
||||||
|
mPhoneStoragePermissionButton.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
|
R.drawable.ic_permission_approved_bw, 0, 0, 0);
|
||||||
|
} else {
|
||||||
|
mPhoneStoragePermissionButton.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
|
R.drawable.ic_permission_approved, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (isAmbient()) {
|
||||||
|
mPhoneStoragePermissionButton.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
|
R.drawable.ic_permission_denied_bw, 0, 0, 0);
|
||||||
|
} else {
|
||||||
|
mPhoneStoragePermissionButton.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
|
R.drawable.ic_permission_denied, 0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Handles all messages for the UI coming on and off the main thread. Not all callbacks happen
|
||||||
|
* on the main thread.
|
||||||
|
*/
|
||||||
|
private void logToUi(final String message) {
|
||||||
|
|
||||||
|
boolean mainUiThread = (Looper.myLooper() == Looper.getMainLooper());
|
||||||
|
|
||||||
|
if (mainUiThread) {
|
||||||
|
|
||||||
|
if (!message.isEmpty()) {
|
||||||
|
Log.d(TAG, message);
|
||||||
|
mOutputTextView.setText(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (!message.isEmpty()) {
|
||||||
|
Log.d(TAG, message);
|
||||||
|
mOutputTextView.setText(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 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.runtimepermissions;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.wearable.activity.WearableActivity;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asks user if they want to open permission screen on their remote device (phone).
|
||||||
|
*/
|
||||||
|
public class RequestPermissionOnPhoneActivity extends WearableActivity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_request_permission_on_phone);
|
||||||
|
setAmbientEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onClickPermissionPhoneStorage(View view) {
|
||||||
|
setResult(RESULT_OK);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
11
samples/browseable/RuntimePermissionsWear/_index.jd
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
page.tags="RuntimePermissionsWear"
|
||||||
|
sample.group=Wearable
|
||||||
|
@jd:body
|
||||||
|
|
||||||
|
<p>
|
||||||
|
|
||||||
|
A sample that shows how you can handle remote data that requires permissions both on
|
||||||
|
a wearable device and a mobile device.
|
||||||
|
|
||||||
|
</p>
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2014 The Android Open Source Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent" android:layout_height="match_parent">
|
||||||
|
<View
|
||||||
|
android:id="@+id/center"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_centerInParent="true"/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_above="@id/center"
|
||||||
|
android:layout_marginBottom="18dp"
|
||||||
|
android:fontFamily="sans-serif-light"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:text="@string/start_saving_gps"/>
|
||||||
|
<android.support.wearable.view.CircledImageView
|
||||||
|
android:id="@+id/cancelBtn"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_below="@id/center"
|
||||||
|
android:layout_toLeftOf="@id/center"
|
||||||
|
android:layout_marginEnd="10dp"
|
||||||
|
android:src="@drawable/ic_cancel_80"
|
||||||
|
app:circle_color="@color/grey"
|
||||||
|
android:onClick="onClick"
|
||||||
|
app:circle_padding="@dimen/circle_padding"
|
||||||
|
app:circle_radius="@dimen/circle_radius"
|
||||||
|
app:circle_radius_pressed="@dimen/circle_radius_pressed" />
|
||||||
|
<android.support.wearable.view.CircledImageView
|
||||||
|
android:id="@+id/submitBtn"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_below="@id/center"
|
||||||
|
android:layout_toRightOf="@id/center"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:src="@drawable/ic_confirmation_80"
|
||||||
|
app:circle_color="@color/blue"
|
||||||
|
android:onClick="onClick"
|
||||||
|
app:circle_padding="@dimen/circle_padding"
|
||||||
|
app:circle_radius="@dimen/circle_radius"
|
||||||
|
app:circle_radius_pressed="@dimen/circle_radius_pressed" />
|
||||||
|
</RelativeLayout>
|
||||||
@@ -408,6 +408,7 @@ public class WearableMainActivity extends WearableActivity implements
|
|||||||
putDataMapRequest.getDataMap()
|
putDataMapRequest.getDataMap()
|
||||||
.putLong(Constants.KEY_TIME, entry.calendar.getTimeInMillis());
|
.putLong(Constants.KEY_TIME, entry.calendar.getTimeInMillis());
|
||||||
PutDataRequest request = putDataMapRequest.asPutDataRequest();
|
PutDataRequest request = putDataMapRequest.asPutDataRequest();
|
||||||
|
request.setUrgent();
|
||||||
Wearable.DataApi.putDataItem(mGoogleApiClient, request)
|
Wearable.DataApi.putDataItem(mGoogleApiClient, request)
|
||||||
.setResultCallback(new ResultCallback<DataApi.DataItemResult>() {
|
.setResultCallback(new ResultCallback<DataApi.DataItemResult>() {
|
||||||
@Override
|
@Override
|
||||||
|
|||||||