Post on 29-Nov-2014
description
Slightly Advanced Android Wear ;-)
+AlfredoMorresi@rainbowbreeze
UX basics
Design for Wear IS NOT design for a small phone
5 seconds or less per interaction
Design for big gestures
Think about stream cards first
Do one thing, really fast
Design for the corner of the eye
Don’t be a constant shoulder tapper
Activity Lifecycle
Voice Capabilities
<activity android:name="MyNoteActivity">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="com.google.android.voicesearch.SELF_NOTE" />
</intent-filter>
</activity>
AndroidManifest.xml
<application>
<activity
android:name="StartRunActivity"
android:label="MyRunningApp">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
AndroidManifest.xml
private static final int SPEECH_REQUEST_CODE = 0;
// Create an intent that can start the Speech Recognizer activity
private void displaySpeechRecognizer() {
Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
// Start the activity, the intent will be populated with the speech text
startActivityForResult(intent, SPEECH_REQUEST_CODE);
}
MyActivity.java
// This callback is invoked when the Speech Recognizer returns.
// This is where you process the intent and extract the speech text from
the intent.
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
if (requestCode == SPEECH_REQUEST_CODE && resultCode == RESULT_OK) {
List<String> results = data.getStringArrayListExtra(
RecognizerIntent.EXTRA_RESULTS);
String spokenText = results.get(0);
// Do something with spokenText
}
super.onActivityResult(requestCode, resultCode, data);
}
MyActivity.java
Notifications background
Background
400x400640x400 (parallax scrolling)
res/drawable-nodpi(background)
res/drawable-hdpi(all the other icons)
// Create a WearableExtender to add functionality
// for wearables
NotificationCompat.WearableExtender wearableExtender =
new NotificationCompat.WearableExtender()
.setHintHideIcon(true)
.setBackground(mBitmap);
MyClass.java
Data APIs
WearableListenerService(background)
DataApi.DataListener(for Activity)
PutDataMapRequest putDataMapRequest =
PutDataMapRequest.create(Constant.LANDING_SITE_RESPONSE);
DataMap dataMap = putDataMapRequest.getDataMap();
dataMap.putInt(Constant.KEY_MISSION_NUMBER, 123);
dataMap.putString(Constant.KEY_LANDING_SITE_NAME, "...");
dataMap.putLong(Constant.KEY_TIMESTAMP, new Date().getTime());
PutDataRequest request = putDataMapRequest.asPutDataRequest();
PendingResult<DataApi.DataItemResult> pendingResult =
Wearable.DataApi.putDataItem(mDataClient, request);
DataApi.DataItemResult result = pendingResult.await();
Avoid DataAPI caching
Data Sync & Messaging Library for Android Wear
Mariuxtheone / Teleport
Networking on Wear
HttpUrlConnect IS NOT available
Doh!
Transferring bitmaps(handheld side)
String url = "...";
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
byte[] byteChunk = new byte[1024];
int n;
InputStream in = new java.net.URL(url).openStream();
while ( (n = in.read(byteChunk)) > 0 ) {
outputStream.write(byteChunk, 0, n);
}
// Check for bitmap correctness …
Asset asset = Asset.createFromBytes(outputStream.toByteArray());
}
PutDataMapRequest dataMap = PutDataMapRequest.create("/image");
dataMap.getDataMap().putAsset("profileImage", asset)
dataMap.getDataMap().putLong("timestamp", new Date().getTime());
PutDataRequest request = dataMap.asPutDataRequest();
PendingResult<DataApi.DataItemResult> pendingResult = Wearable.DataApi
.putDataItem(mDataClient, request);
DataApi.DataItemResult result = pendingResult.await();
// result.getDataItem().getUri());
private boolean isDataAValidBitmap(byte[] data)
Bitmap checkBitmap = BitmapFactory.decodeByteArray(
data, 0, data.length);
// Checks bitmap for null or sizes with .getWidth()
return null != checkBitmap;
}
// From Bitmap to asset
private static Asset createAssetFromBitmap(Bitmap bitmap) {
final ByteArrayOutputStream bs =
new ByteArrayOutputStream();
// Resize or apply other transformations...
bitmap.compress(Bitmap.CompressFormat.PNG, 100, bs);
return Asset.createFromBytes(bs.toByteArray());
}
Bitmap bitmap = BitmapFactory.
decodeResource(
getResources(),
R.drawable.image);
From resources
String url = "...";
ImageRequest request = new ImageRequest(url,
new Response.Listener() {
@Override
public void onResponse(Bitmap bitmap) {
// Your Android Wear code here
}
}, 0, 0, null,
new Response.ErrorListener() {
public void onErrorResponse(VolleyError error) {
// Manages loading errors
}
});
// Access the RequestQueue through your singleton class.
MySingleton.getInstance(this).addToRequestQueue(request);
Using Volley
Picasso.with(context)
.load(url)
.resize(50, 50)
.transform(myTransformation)
.into(myTarget)
// Put transferring logic inside MyTarget class
Using Picasso
Transferring bitmaps(Wear side)
@Override
public void onDataChanged(DataEventBuffer dataEvents) {
for (DataEvent event : dataEvents) {
if (event.getType() == DataEvent.TYPE_CHANGED &&
event.getDataItem().getUri().getPath().equals("/image")) {
DataMapItem dataMapItem =
DataMapItem.fromDataItem(event.getDataItem());
Asset profileAsset =
dataMapItem.getDataMap().getAsset("profileImage");
Bitmap bitmap = loadBitmapFromAsset(profileAsset);
// Do something with the bitmap
}
}
}
public Bitmap loadBitmapFromAsset(Asset asset) {
if (asset == null) {
throw new IllegalArgumentException("Asset must be non-null");
}
GoogleApiClient mGoogleApiClient = new GoogleApiClient.Builder(context)
.addApi(Wearable.API)
.build();
ConnectionResult result =
mGoogleApiClient.blockingConnect(TIMEOUT_MS, TimeUnit.MILLISECONDS);
if (!result.isSuccess()) {
return null;
}
// convert asset into a file descriptor and block until it's ready
InputStream assetInputStream = Wearable.DataApi.getFdForAsset(
mGoogleApiClient, asset).await().getInputStream();
mGoogleApiClient.disconnect();
if (assetInputStream == null) {
Log.w(TAG, "Requested an unknown Asset.");
return null;
}
// decode the stream into a bitmap
return BitmapFactory.decodeStream(assetInputStream);
}
The mark of a great man is one who knows when to set aside the important things in order to accomplish the vital ones. Brandon Sanderson
“’’
takahirom / WearHttp
Advanced UI
Custom Notifications
Create custom activity
Display it as notification
<activity
android:name="com.example.NotificationActivity"
android:exported="true"
android:allowEmbedded="true"
android:taskAffinity=""
android:theme="@android:style/Theme.DeviceDefault.Light"
/>
AndroidManifest.xml
public class NotificationActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_notification);
mImageView = (ImageView) findViewById(R.id.image_view);
mTextView = (TextView) findViewById(R.id.text_view);
Intent intent = getIntent();
if (intent != null) {
mTextView.setText(intent.getStringExtra(EXTRA_TITLE));
final Asset asset = intent.getParcelableExtra(EXTRA_IMAGE);
loadBitmapFromAsset(this, asset, mImageView); // do async!
}
}
NotificationActivity.java
<service android:name=".OngoingNotificationListenerService">
<intent-filter>
<action
android:name="com.google.android.gms.wearable.BIND_LISTENER"
/>
</intent-filter>
</service>
AndroidManifest.xml
public class OngoingNotificationListenerService extends WearableListenerService {
private GoogleApiClient mGoogleApiClient;
@Override
public void onCreate() {
super.onCreate();
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Wearable.API)
.build();
mGoogleApiClient.connect();
}
OngoingNotificationListenerService.java
@Override
public void onDataChanged(DataEventBuffer dataEvents) {
final List<DataEvent> events = FreezableUtils.freezeIterable(dataEvents);
dataEvents.close();
if (!mGoogleApiClient.isConnected()) {
ConnectionResult connectionResult = mGoogleApiClient
.blockingConnect(30, TimeUnit.SECONDS);
if (!connectionResult.isSuccess()) {
Log.e(TAG, "Service failed to connect to GoogleApiClient.");
return;
}
}
OngoingNotificationListenerService.java
for (DataEvent event : events) {
if (event.getType() == DataEvent.TYPE_CHANGED) {
String path = event.getDataItem().getUri().getPath();
if (PATH.equals(path)) {
// Get the data out of the event
DataMapItem dataMapItem =
DataMapItem.fromDataItem(event.getDataItem());
final String title = dataMapItem.getDataMap().getString(KEY_TITLE);
Asset asset = dataMapItem.getDataMap().getAsset(KEY_IMAGE);
OngoingNotificationListenerService.java
// Build the intent to display our custom notification
Intent notificationIntent =
new Intent(this, NotificationActivity.class);
notificationIntent.putExtra(
NotificationActivity.EXTRA_TITLE, title);
notificationIntent.putExtra(
NotificationActivity.EXTRA_IMAGE, asset);
PendingIntent notificationPendingIntent = PendingIntent.getActivity(
this,
0,
notificationIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
OngoingNotificationListenerService.java
// Create the ongoing notification
Notification.Builder notificationBuilder =
new Notification.Builder(this)
.setSmallIcon(R.drawable.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(
getResources(), R.drawable.ic_launcher))
.setOngoing(true)
.extend(new Notification.WearableExtender()
.setDisplayIntent(notificationPendingIntent)
.setBackground(myCustomBackground));
OngoingNotificationListenerService.java
// Build the notification and show it
NotificationManager notificationManager =
(NotificationManager)getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify(
NOTIFICATION_ID, notificationBuilder.build());
} else {
Log.d(TAG, "Unrecognized path: " + path);
}
}
}
}
OngoingNotificationListenerService.java
Disable swipe to dismiss
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
AndroidManifest.xml
<resources>
<style
name="AppTheme"
parent="@android:style/Theme.DeviceDefault.Light"
>
<item name="android:windowSwipeToDismiss">false</item>
</style>
</resources>
Styles.xml
Long press to dismiss
public class ApolloDiceActivity extends Activity implements View.
OnTouchListener, View.OnLongClickListener {
private DismissOverlayView mDismissOverlayView;
@Override
protected void onCreate(Bundle savedInstanceState) {
// ...
RelativeLayout parentFrame = (RelativeLayout)
findViewById(R.id.parent_frame);
// Add DismissOverlayView to cover the whole screen
mDismissOverlayView = new DismissOverlayView(this);
parentFrame.addView(mDismissOverlayView,new RelativeLayout.
LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT,
RelativeLayout.LayoutParams.MATCH_PARENT));
parentFrame.setOnLongClickListener(this);
// Shows the default dismiss overlay
public boolean onLongClick(View v) {
mDismissOverlayView.show();
return true;
}
// Set onTouch to return false so it doesn’t
// intercept the LongClick event:
public boolean onTouch(View v, MotionEvent event) {
//...
return false;
}
Round and Rect layout
<?xml version="1.0" encoding="utf-8"?>
<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_match_timer"
app:roundLayout="@layout/round_activity_match_timer"
tools:context=".MatchTimerNotificationActivity"
tools:deviceIds="wear" />
res/layout/activity_match_timer.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/timer"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MatchTimerNotificationActivity"
tools:deviceIds="wear_square">
<TextView
android:id="@+id/elapsed"
res/layout/rect_activity_match_timer.xml
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_match_timer);
final WatchViewStub stub = (WatchViewStub) findViewById(R.id.watch_view_stub);
stub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener()
{
@Override
public void onLayoutInflated(WatchViewStub stub) {
elapsed = (TextView) stub.findViewById(R.id.elapsed);
// ...
}
});
}
MatchTimerNotificationActivity.java
Complex examples
What’s next?Getting Started with Android Weardeveloper.android.com/wear
Developers Italiadevelopersitalia.blogspot.com
+AlfredoMorresi@rainbowbreeze
Thank you!
Credits:Hoi Lam: http://viewbeyond.blogspot.co.uk
Styling Android: http://blog.stylingandroid.comImages: all images are under CC licence