Add new SynchronizedNotifications sample.
This sample demonstrates how to use DataItems to construct linked phone- and wearable-side notifications. Change-Id: I6defa7fad671ba2ec0de25c8913abd66d07846a2
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.android.wearable.synchronizednotifications" >
|
||||
<uses-sdk android:minSdkVersion="18" android:targetSdkVersion="19" />
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@android:style/Theme.DeviceDefault.Light" >
|
||||
<meta-data android:name="com.google.android.gms.version"
|
||||
android:value="@integer/google_play_services_version" />
|
||||
<activity
|
||||
android:name=".PhoneActivity"
|
||||
android:label="@string/app_name" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<service android:name=".DismissListener">
|
||||
<intent-filter>
|
||||
<action
|
||||
android:name="com.google.android.gms.wearable.BIND_LISTENER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action
|
||||
android:name="com.example.android.wearable.synchronizednotifications.DISMISS" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.example.android.wearable.synchronizednotifications;
|
||||
|
||||
import static com.google.android.gms.wearable.PutDataRequest.WEAR_URI_SCHEME;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.NotificationManagerCompat;
|
||||
import android.util.Log;
|
||||
|
||||
import com.example.android.wearable.synchronizednotifications.common.Constants;
|
||||
import com.google.android.gms.common.ConnectionResult;
|
||||
import com.google.android.gms.common.api.GoogleApiClient;
|
||||
import com.google.android.gms.common.api.ResultCallback;
|
||||
import com.google.android.gms.wearable.DataApi;
|
||||
import com.google.android.gms.wearable.DataEvent;
|
||||
import com.google.android.gms.wearable.DataEventBuffer;
|
||||
import com.google.android.gms.wearable.PutDataMapRequest;
|
||||
import com.google.android.gms.wearable.Wearable;
|
||||
import com.google.android.gms.wearable.WearableListenerService;
|
||||
|
||||
/**
|
||||
* A {@link com.google.android.gms.wearable.WearableListenerService} that is invoked when certain
|
||||
* notifications are dismissed from either the phone or watch.
|
||||
*/
|
||||
public class DismissListener extends WearableListenerService
|
||||
implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener,
|
||||
ResultCallback<DataApi.DeleteDataItemsResult> {
|
||||
|
||||
private static final String TAG = "DismissListener";
|
||||
private GoogleApiClient mGoogleApiClient;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
mGoogleApiClient = new GoogleApiClient.Builder(this)
|
||||
.addApi(Wearable.API)
|
||||
.addConnectionCallbacks(this)
|
||||
.addOnConnectionFailedListener(this)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataChanged(DataEventBuffer dataEvents) {
|
||||
for (DataEvent dataEvent : dataEvents) {
|
||||
if (dataEvent.getType() == DataEvent.TYPE_DELETED) {
|
||||
if (Constants.BOTH_PATH.equals(dataEvent.getDataItem().getUri().getPath())) {
|
||||
// notification on the phone should be dismissed
|
||||
NotificationManagerCompat.from(this).cancel(Constants.BOTH_ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if (null != intent) {
|
||||
String action = intent.getAction();
|
||||
if (Constants.ACTION_DISMISS.equals(action)) {
|
||||
// We need to dismiss the wearable notification. We delete the DataItem that
|
||||
// created the notification to inform the wearable.
|
||||
int notificationId = intent.getIntExtra(Constants.KEY_NOTIFICATION_ID, -1);
|
||||
if (notificationId == Constants.BOTH_ID) {
|
||||
dismissWearableNotification(notificationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.onStartCommand(intent, flags, startId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the DataItem that was used to create a notification on the watch. By deleting the
|
||||
* data item, a {@link com.google.android.gms.wearable.WearableListenerService} on the watch
|
||||
* will be notified and the notification on the watch will be removed.
|
||||
*
|
||||
* @param id The ID of the notification that should be removed
|
||||
*/
|
||||
private void dismissWearableNotification(final int id) {
|
||||
mGoogleApiClient.connect();
|
||||
}
|
||||
|
||||
@Override // ConnectionCallbacks
|
||||
public void onConnected(Bundle bundle) {
|
||||
final Uri dataItemUri =
|
||||
new Uri.Builder().scheme(WEAR_URI_SCHEME).path(Constants.BOTH_PATH).build();
|
||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, "Deleting Uri: " + dataItemUri.toString());
|
||||
}
|
||||
Wearable.DataApi.deleteDataItems(
|
||||
mGoogleApiClient, dataItemUri).setResultCallback(this);
|
||||
}
|
||||
|
||||
@Override // ConnectionCallbacks
|
||||
public void onConnectionSuspended(int i) {
|
||||
}
|
||||
|
||||
@Override // OnConnectionFailedListener
|
||||
public void onConnectionFailed(ConnectionResult connectionResult) {
|
||||
Log.e(TAG, "Failed to connect to the Google API client");
|
||||
}
|
||||
|
||||
@Override // ResultCallback<DataApi.DeleteDataItemsResult>
|
||||
public void onResult(DataApi.DeleteDataItemsResult deleteDataItemsResult) {
|
||||
if (!deleteDataItemsResult.getStatus().isSuccess()) {
|
||||
Log.e(TAG, "dismissWearableNotification(): failed to delete DataItem");
|
||||
}
|
||||
mGoogleApiClient.disconnect();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.example.android.wearable.synchronizednotifications;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.NotificationManagerCompat;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import com.example.android.wearable.synchronizednotifications.common.Constants;
|
||||
import com.google.android.gms.common.ConnectionResult;
|
||||
import com.google.android.gms.common.api.GoogleApiClient;
|
||||
import com.google.android.gms.common.api.ResultCallback;
|
||||
import com.google.android.gms.wearable.DataApi;
|
||||
import com.google.android.gms.wearable.PutDataMapRequest;
|
||||
import com.google.android.gms.wearable.PutDataRequest;
|
||||
import com.google.android.gms.wearable.Wearable;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* A simple activity that presents three buttons that would trigger three different combinations of
|
||||
* notifications on the handset and the watch:
|
||||
* <ul>
|
||||
* <li>The first button builds a simple local-only notification on the handset.</li>
|
||||
* <li>The second one creates a wearable-only notification by putting a data item in the shared data
|
||||
* store and having a {@link com.google.android.gms.wearable.WearableListenerService} listen for
|
||||
* that on the wearable</li>
|
||||
* <li>The third one creates a local notification and a wearable notification by combining the above
|
||||
* two. It, however, demonstrates how one can set things up so that the dismissal of one
|
||||
* notification results in the dismissal of the other one.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class PhoneActivity extends Activity implements GoogleApiClient.ConnectionCallbacks,
|
||||
GoogleApiClient.OnConnectionFailedListener {
|
||||
|
||||
private static final String TAG = "PhoneActivity";
|
||||
private GoogleApiClient mGoogleApiClient;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_phone);
|
||||
mGoogleApiClient = new GoogleApiClient.Builder(this)
|
||||
.addApi(Wearable.API)
|
||||
.addConnectionCallbacks(this)
|
||||
.addOnConnectionFailedListener(this)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a local-only notification for the handset. This is achieved by using
|
||||
* <code>setLocalOnly(true)</code>. If <code>withDismissal</code> is set to <code>true</code>, a
|
||||
* {@link android.app.PendingIntent} will be added to handle the dismissal of notification to
|
||||
* be able to remove the mirrored notification on the wearable.
|
||||
*/
|
||||
private void buildLocalOnlyNotification(String title, String content, int notificationId,
|
||||
boolean withDismissal) {
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
|
||||
builder.setContentTitle(title)
|
||||
.setContentText(content)
|
||||
.setLocalOnly(true)
|
||||
.setSmallIcon(R.drawable.ic_launcher);
|
||||
|
||||
if (withDismissal) {
|
||||
Intent dismissIntent = new Intent(Constants.ACTION_DISMISS);
|
||||
dismissIntent.putExtra(Constants.KEY_NOTIFICATION_ID, Constants.BOTH_ID);
|
||||
PendingIntent pendingIntent = PendingIntent
|
||||
.getService(this, 0, dismissIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
builder.setDeleteIntent(pendingIntent);
|
||||
}
|
||||
NotificationManagerCompat.from(this).notify(notificationId, builder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a DataItem that on the wearable will be interpreted as a request to show a
|
||||
* notification. The result will be a notification that only shows up on the wearable.
|
||||
*/
|
||||
private void buildWearableOnlyNotification(String title, String content, String path) {
|
||||
if (mGoogleApiClient.isConnected()) {
|
||||
PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(path);
|
||||
putDataMapRequest.getDataMap().putString(Constants.KEY_CONTENT, content);
|
||||
putDataMapRequest.getDataMap().putString(Constants.KEY_TITLE, title);
|
||||
PutDataRequest request = putDataMapRequest.asPutDataRequest();
|
||||
Wearable.DataApi.putDataItem(mGoogleApiClient, request)
|
||||
.setResultCallback(new ResultCallback<DataApi.DataItemResult>() {
|
||||
@Override
|
||||
public void onResult(DataApi.DataItemResult dataItemResult) {
|
||||
if (!dataItemResult.getStatus().isSuccess()) {
|
||||
Log.e(TAG, "buildWatchOnlyNotification(): Failed to set the data, "
|
||||
+ "status: " + dataItemResult.getStatus().getStatusCode());
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Log.e(TAG, "buildWearableOnlyNotification(): no Google API Client connection");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a local notification and sets a DataItem that will be interpreted by the wearable as
|
||||
* a request to build a notification on the wearable as as well. The two notifications show
|
||||
* different messages.
|
||||
* Dismissing either of the notifications will result in dismissal of the other; this is
|
||||
* achieved by creating a {@link android.app.PendingIntent} that results in removal of
|
||||
* the DataItem that created the watch notification. The deletion of the DataItem is observed on
|
||||
* both sides, using WearableListenerService callbacks, and is interpreted on each side as a
|
||||
* request to dismiss the corresponding notification.
|
||||
*/
|
||||
private void buildMirroredNotifications(String phoneTitle, String watchTitle, String content) {
|
||||
if (mGoogleApiClient.isConnected()) {
|
||||
// Wearable notification
|
||||
buildWearableOnlyNotification(watchTitle, content, Constants.BOTH_PATH);
|
||||
|
||||
// Local notification, with a pending intent for dismissal
|
||||
buildLocalOnlyNotification(phoneTitle, content, Constants.BOTH_ID, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
mGoogleApiClient.connect();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
mGoogleApiClient.disconnect();
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnected(Bundle bundle) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionSuspended(int i) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionFailed(ConnectionResult connectionResult) {
|
||||
Log.e(TAG, "Failed to connect to Google API Client");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string built from the current time
|
||||
*/
|
||||
private String now() {
|
||||
DateFormat dateFormat = android.text.format.DateFormat.getTimeFormat(this);
|
||||
return dateFormat.format(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles button clicks in the UI.
|
||||
*/
|
||||
public void onClick(View view) {
|
||||
int id = view.getId();
|
||||
switch (id) {
|
||||
case R.id.phone_only:
|
||||
buildLocalOnlyNotification(getString(R.string.phone_only), now(),
|
||||
Constants.PHONE_ONLY_ID, false);
|
||||
break;
|
||||
case R.id.wear_only:
|
||||
buildWearableOnlyNotification(getString(R.string.wear_only), now(),
|
||||
Constants.WATCH_ONLY_PATH);
|
||||
break;
|
||||
case R.id.different_notifications:
|
||||
buildMirroredNotifications(getString(R.string.phone_both), getString(R.string.watch_both), now());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 297 B |
Binary file not shown.
|
After Width: | Height: | Size: 254 B |
Binary file not shown.
|
After Width: | Height: | Size: 342 B |
Binary file not shown.
|
After Width: | Height: | Size: 471 B |
@@ -0,0 +1,41 @@
|
||||
<RelativeLayout 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:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
tools:context=".PhoneActivity">
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/phone_only"
|
||||
android:id="@+id/phone_only"
|
||||
android:onClick="onClick"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_centerHorizontal="true" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/wear_only"
|
||||
android:id="@+id/wear_only"
|
||||
android:onClick="onClick"
|
||||
android:layout_below="@+id/phone_only"
|
||||
android:layout_alignLeft="@+id/phone_only"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_alignRight="@+id/phone_only" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/different_notifications"
|
||||
android:id="@+id/different_notifications"
|
||||
android:layout_below="@+id/wear_only"
|
||||
android:onClick="onClick"
|
||||
android:layout_alignLeft="@+id/wear_only"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_alignRight="@+id/wear_only" />
|
||||
</RelativeLayout>
|
||||
@@ -0,0 +1,5 @@
|
||||
<resources>
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
</resources>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="app_name">Synchronized Notifications</string>
|
||||
<string name="wear_only">Watch Only Notification</string>
|
||||
<string name="phone_only">Phone Only Notification</string>
|
||||
<string name="different_notifications">Different Notifications</string>
|
||||
<string name="phone_both">Phone Notification</string>
|
||||
<string name="watch_both">Watch Notification</string>
|
||||
|
||||
</resources>
|
||||
Reference in New Issue
Block a user