Update sample prebuilts for mnc-docs

Synced to //developers/samples/android commit
243feb49e8d1753b746f69ae5519eaace0e50605.

Change-Id: I9255d2ad8f68669d77124b7840184171fb5a801b
This commit is contained in:
Trevor Johns
2016-01-08 16:54:53 -08:00
parent f90e19f5ce
commit 419a0f19bc
113 changed files with 3036 additions and 38 deletions

View File

@@ -250,6 +250,11 @@ public class CalendarQueryService extends IntentService
public PutDataMapRequest toPutDataMapRequest(){
final PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(
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();
data.putString(DATA_ITEM_URI, putDataMapRequest.getUri().toString());
data.putLong(ID, id);

View File

@@ -48,6 +48,7 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.support.annotation.NonNull;
import android.support.v13.app.FragmentCompat;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.util.Size;
import android.util.SparseIntArray;
@@ -269,6 +270,11 @@ public class Camera2BasicFragment extends Fragment
*/
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.
*/
@@ -568,6 +574,10 @@ public class Camera2BasicFragment extends Fragment
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;
return;
}
@@ -585,7 +595,7 @@ public class Camera2BasicFragment extends Fragment
* Opens the camera specified by {@link Camera2BasicFragment#mCameraId}.
*/
private void openCamera(int width, int height) {
if (getActivity().checkSelfPermission(Manifest.permission.CAMERA)
if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
requestCameraPermission();
return;
@@ -691,8 +701,7 @@ public class Camera2BasicFragment extends Fragment
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// Flash is automatically enabled when necessary.
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
setAutoFlash(mPreviewRequestBuilder);
// Finally, we start displaying the camera preview.
mPreviewRequest = mPreviewRequestBuilder.build();
@@ -808,8 +817,7 @@ public class Camera2BasicFragment extends Fragment
// Use the same AE and AF modes as the preview.
captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
captureBuilder.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
setAutoFlash(captureBuilder);
// Orientation
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
@@ -844,8 +852,7 @@ public class Camera2BasicFragment extends Fragment
// Reset the auto-focus trigger
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
setAutoFlash(mPreviewRequestBuilder);
mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
mBackgroundHandler);
// 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}.
*/

View File

@@ -381,7 +381,9 @@ public class MainActivity extends Activity implements DataApi.DataListener,
public void run() {
PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(COUNT_PATH);
putDataMapRequest.getDataMap().putInt(COUNT_KEY, count++);
putDataMapRequest.setUrgent();
PutDataRequest request = putDataMapRequest.asPutDataRequest();
request.setUrgent();
LOGD(TAG, "Generating DataItem: " + request);
if (!mGoogleApiClient.isConnected()) {
@@ -442,6 +444,8 @@ public class MainActivity extends Activity implements DataApi.DataListener,
dataMap.getDataMap().putAsset(IMAGE_KEY, asset);
dataMap.getDataMap().putLong("time", new Date().getTime());
PutDataRequest request = dataMap.asPutDataRequest();
request.setUrgent();
Wearable.DataApi.putDataItem(mGoogleApiClient, request)
.setResultCallback(new ResultCallback<DataItemResult>() {
@Override

View File

@@ -20,6 +20,7 @@
android:layout_height="fill_parent" >
<ProgressBar
android:id="@+id/progressbar"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

View File

@@ -23,6 +23,7 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ProgressBar;
import com.example.android.displayingbitmaps.R;
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}.
*/
public class ImageDetailFragment extends Fragment {
public class ImageDetailFragment extends Fragment implements ImageWorker.OnImageLoadedListener {
private static final String IMAGE_DATA_EXTRA = "extra_image_data";
private String mImageUrl;
private ImageView mImageView;
private ProgressBar mProgressBar;
private ImageFetcher mImageFetcher;
/**
@@ -75,6 +77,7 @@ public class ImageDetailFragment extends Fragment {
// Inflate and locate the main ImageView
final View v = inflater.inflate(R.layout.image_detail_fragment, container, false);
mImageView = (ImageView) v.findViewById(R.id.imageView);
mProgressBar = (ProgressBar) v.findViewById(R.id.progressbar);
return v;
}
@@ -86,7 +89,7 @@ public class ImageDetailFragment extends Fragment {
// cache can be used over all pages in the ViewPager
if (ImageDetailActivity.class.isInstance(getActivity())) {
mImageFetcher = ((ImageDetailActivity) getActivity()).getImageFetcher();
mImageFetcher.loadImage(mImageUrl, mImageView);
mImageFetcher.loadImage(mImageUrl, mImageView, this);
}
// Pass clicks on the ImageView to the parent activity to handle
@@ -104,4 +107,11 @@ public class ImageDetailFragment extends Fragment {
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);
}
}

View File

@@ -71,8 +71,9 @@ public abstract class ImageWorker {
*
* @param data The URL of the image to download.
* @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) {
return;
}
@@ -86,9 +87,12 @@ public abstract class ImageWorker {
if (value != null) {
// Bitmap found in memory cache
imageView.setImageDrawable(value);
if (listener != null) {
listener.onImageLoaded(true);
}
} else if (cancelPotentialWork(data, imageView)) {
//BEGIN_INCLUDE(execute_background_task)
final BitmapWorkerTask task = new BitmapWorkerTask(data, imageView);
final BitmapWorkerTask task = new BitmapWorkerTask(data, imageView, listener);
final AsyncDrawable asyncDrawable =
new AsyncDrawable(mResources, mLoadingBitmap, task);
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.
*
@@ -238,10 +257,18 @@ public abstract class ImageWorker {
private class BitmapWorkerTask extends AsyncTask<Void, Void, BitmapDrawable> {
private Object mData;
private final WeakReference<ImageView> imageViewReference;
private final OnImageLoadedListener mOnImageLoadedListener;
public BitmapWorkerTask(Object data, ImageView imageView) {
mData = data;
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
protected void onPostExecute(BitmapDrawable value) {
//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 (isCancelled() || mExitTasksEarly) {
value = null;
@@ -328,8 +356,12 @@ public abstract class ImageWorker {
if (BuildConfig.DEBUG) {
Log.d(TAG, "onPostExecute - setting bitmap");
}
success = true;
setImageDrawable(imageView, value);
}
if (mOnImageLoadedListener != null) {
mOnImageLoadedListener.onImageLoaded(success);
}
//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.
* Contains a reference to the actual worker task, so that it can be stopped if a new binding is

View File

@@ -100,6 +100,7 @@ public class FindPhoneService extends IntentService implements GoogleApiClient.C
// when it receives the change.
PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(PATH_SOUND_ALARM);
putDataMapRequest.getDataMap().putBoolean(FIELD_ALARM_ON, alarmOn);
putDataMapRequest.setUrgent();
Wearable.DataApi.putDataItem(mGoogleApiClient, putDataMapRequest.asPutDataRequest())
.await();
} else {

View File

@@ -89,6 +89,7 @@ public class GeofenceTransitionsIntentService extends IntentService
final PutDataMapRequest putDataMapRequest =
PutDataMapRequest.create(GEOFENCE_DATA_ITEM_PATH);
putDataMapRequest.getDataMap().putString(KEY_GEOFENCE_ID, triggeredGeoFenceId);
putDataMapRequest.setUrgent();
if (mGoogleApiClient.isConnected()) {
Wearable.DataApi.putDataItem(
mGoogleApiClient, putDataMapRequest.asPutDataRequest()).await();

View File

@@ -248,7 +248,9 @@ public class MainActivity extends Activity implements DataApi.DataListener,
dataMap.putInt(QUESTION_INDEX, questionIndex);
dataMap.putStringArray(ANSWERS, answers);
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);
if (!mHasQuestionBeenAsked && dataMap.getInt(QUESTION_INDEX) == 0) {
// 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);
} else {
// Enqueue future questions.

View File

@@ -76,6 +76,7 @@ public class DeleteQuestionService extends IntentService
DataMap dataMap = putDataMapRequest.getDataMap();
dataMap.putBoolean(QUESTION_WAS_DELETED, true);
PutDataRequest request = putDataMapRequest.asPutDataRequest();
request.setUrgent();
Wearable.DataApi.putDataItem(mGoogleApiClient, request).await();
mGoogleApiClient.disconnect();
}

View File

@@ -88,6 +88,7 @@ public class UpdateQuestionService extends IntentService
dataMap.putBoolean(CHOSEN_ANSWER_CORRECT, chosenAnswerCorrect);
dataMap.putBoolean(QUESTION_WAS_ANSWERED, true);
PutDataRequest request = putDataMapRequest.asPutDataRequest();
request.setUrgent();
Wearable.DataApi.putDataItem(mGoogleApiClient, request).await();
// Remove this question notification.

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 654 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

@@ -0,0 +1,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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 843 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 679 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 654 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

@@ -0,0 +1,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>

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

@@ -408,6 +408,7 @@ public class WearableMainActivity extends WearableActivity implements
putDataMapRequest.getDataMap()
.putLong(Constants.KEY_TIME, entry.calendar.getTimeInMillis());
PutDataRequest request = putDataMapRequest.asPutDataRequest();
request.setUrgent();
Wearable.DataApi.putDataItem(mGoogleApiClient, request)
.setResultCallback(new ResultCallback<DataApi.DataItemResult>() {
@Override

Some files were not shown because too many files have changed in this diff Show More