Question

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" />
Was it helpful?

Solution

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:

        <service
        android:name="com.example.lowfrequencylivecardexample.LiveCardService"
        android:enabled="true"
        android:exported="true"
        android:label="@string/app_name" >
        <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" />
    </service>

You also need:

    <uses-permission android:name="com.google.android.glass.permission.DEVELOPMENT" />

You will also need this file ... res/xml/voice_trigger.xml:

<?xml version="1.0" encoding="utf-8"?>
<trigger keyword="basketball" />

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!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top