I try to implement low-frequency Live Card using the instruction provided in the GDK guides. I have a layout that I want to render and the LiveCard service class (that extends Service). I also have the Menu: activity to handle menu callbacks, making the menu transparent, and provide a PendingIntent for the card's action using setAction() in the LiveCard service.
I also got a successful message when loading the app into Glass but it doesn't show up in my Glass. I'm not sure what else is missing.
[2014-04-22 00:45:01 - MyApp] Installing MyApp.apk...
[2014-04-22 00:45:04 - MyApp] Success!
[2014-04-22 00:45:04 - MyApp] /MyApp/bin/MyApp.apk installed on device
[2014-04-22 00:45:04 - MyApp] Done!
Below is my LiveCardService:
public class LiveCardService extends Service {
private ArrayList<FeedItem> feedItems = new ArrayList<FeedItem>();
//private FeedAdapter feedAdapter = null;
// NYC: 40.758895, -73.985131
private double latitude = 0;
private double longitude = 0;
private LocationManager mlocManager;
private LocationListener mlocListener;
/******/
private static final String LIVE_CARD_TAG = "LiveCardDemo";
//private TimelineManager mTimelineManager;
private LiveCard mLiveCard;
private RemoteViews mLiveCardView;
private int homeScore, awayScore;
private Random mPointsGenerator;
private final Handler mHandler = new Handler();
private final UpdateLiveCardRunnable mUpdateLiveCardRunnable =
new UpdateLiveCardRunnable();
private static final long DELAY_MILLIS = 30000;
@Override
public void onCreate() {
super.onCreate();
//mTimelineManager = TimelineManager.from(this);
mPointsGenerator = new Random();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (mLiveCard == null) {
// Get an instance of a live card
// mLiveCard = mTimelineManager.createLiveCard(LIVE_CARD_TAG);
mLiveCard = new LiveCard(this, LIVE_CARD_TAG);
// Inflate a layout into a remote view
mLiveCardView = new RemoteViews(getPackageName(),
R.layout.score);
mLiveCard.setViews(mLiveCardView); // !!!
// Set up initial RemoteViews values
homeScore = 0;
awayScore = 0;
/**/
mLiveCardView.setTextViewText(R.id.homeTeamNameTextView,
/*getString(R.id.home_team)*/"HOME TEAM");
mLiveCardView.setTextViewText(R.id.awayTeamNameTextView,
/*getString(R.id.away_team)*/"AWAY TEAM NAME");
mLiveCardView.setTextViewText(R.id.footer_text,
/*getString(R.id.game_quarter)*/"FOOTER_TEXT");
/**/
// Set up the live card's action with a pending intent
// to show a menu when tapped
Intent menuIntent = new Intent(this, MenuActivity.class);
menuIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_CLEAR_TASK);
mLiveCard.setAction(PendingIntent.getActivity(
this, 0, menuIntent, 0));
// Publish the live card
mLiveCard.publish(PublishMode.REVEAL);
// Queue the update text runnable
mHandler.post(mUpdateLiveCardRunnable);
}
return START_STICKY;
}
@Override
public void onDestroy() {
if (mLiveCard != null && mLiveCard.isPublished()) {
//Stop the handler from queuing more Runnable jobs
mUpdateLiveCardRunnable.setStop(true);
mLiveCard.unpublish();
mLiveCard = null;
}
super.onDestroy();
}
/**
* Runnable that updates live card contents
*/
private class UpdateLiveCardRunnable implements Runnable{
private boolean mIsStopped = false;
/*
* Updates the card with a fake score every 30 seconds as a demonstration.
* You also probably want to display something useful in your live card.
*
* If you are executing a long running task to get data to update a
* live card(e.g, making a web call), do this in another thread or
* AsyncTask.
*/
public void run(){
if(!isStopped()){
// Generate fake points.
homeScore += mPointsGenerator.nextInt(3);
awayScore += mPointsGenerator.nextInt(3);
// Update the remote view with the new scores.
mLiveCardView.setTextViewText(R.id.home_score_text_view,
String.valueOf(homeScore));
mLiveCardView.setTextViewText(R.id.away_score_text_view,
String.valueOf(awayScore));
// Always call setViews() to update the live card's RemoteViews.
mLiveCard.setViews(mLiveCardView);
// Queue another score update in 30 seconds.
mHandler.postDelayed(mUpdateLiveCardRunnable, DELAY_MILLIS);
}
}
public boolean isStopped() {
return mIsStopped;
}
public void setStop(boolean isStopped) {
this.mIsStopped = isStopped;
}
}
@Override
public IBinder onBind(Intent intent) {
/*
* If you need to set up interprocess communication
* (activity to a service, for instance), return a binder object
* so that the client can receive and modify data in this service.
*
* A typical use is to give a menu activity access to a binder object
* if it is trying to change a setting that is managed by the live card
* service. The menu activity in this sample does not require any
* of these capabilities, so this just returns null.
*/
return null;
}
and here is my Manifest file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.coeverywhere.google_glass"
android:versionCode="5"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="15"
android:targetSdkVersion="15" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="com.google.android.glass.permission.DEVELOPMENT" />
<application
android:allowBackup="true"
android:icon="@drawable/mylogo"
android:label="@string/app_name">
<activity
android:name="com.myapp.google_glass.MenuActivity"
android:theme="@style/MenuTheme"
android:enabled="true"
>
</activity>
<!-- android:icon="@drawable/ic_lap" -->
<service
android:name="com.myapp.google_glass.LiveCardService"
android:label="@string/app_name"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.google.android.glass.action.VOICE_TRIGGER" />
</intent-filter>
<meta-data
android:name="com.google.android.glass.VoiceTrigger"
android:resource="@xml/voice_trigger_start" />
</service>
</application>
</manifest>
and here's my voice_trigger_start.xml. I put them under res/xml
<?xml version="1.0" encoding="utf-8"?>
<trigger command = "basketball" />
Below is original answer, I have since made a reference project that takes the Google documentation and updates it for XE16 using the StopWatch project as a reference. Check the code and commit history to learn a lot more:
https://github.com/mscheel/GoogleGlass-XE16-LowFrequencyLiveCardBasketballScore
One way is to declare a voice trigger to start the service/livecard.
This is mentioned in this pattern explanation:
https://developers.google.com/glass/develop/patterns/ongoing-task
The technique is described here:
https://developers.google.com/glass/develop/gdk/starting-glassware#unlisted_commands
I tested it out with your code and it worked if the manifest has these items (modify for your package name of course) ... this first one goes inside the application tag:
You also need:
You will also need this file ... res/xml/voice_trigger.xml:
I found the Timer project to be helpful in coming up with this, here is its manifest:
https://github.com/googleglass/gdk-timer-sample/blob/master/AndroidManifest.xml
Optionally you can make the home team the Celtics and the away team the Lakers. Larry Legend forever!