如何在从服务调用接收异步数据时更新 Google Glass 菜单

How to update Google Glass menu when receiving async data from service call

以下是我的设置。 我有一个 livecard 服务 class,它执行异步任务以从外部获取天气和天气预报数据。 它还启动了一个 Pendingintent,其中有两个菜单项 "ShowForecast" & "Stop"

天气数据到达时显示在主屏幕上。 然而,预测数据需要更长的时间。我想隐藏 ShowForecast 菜单,直到异步任务成功完成。

实现这个的最佳方法是什么?我已经阅读了有关全局变量的内容,或者通过 intent.putextra 或直接更新卡片菜单。 我现在想的是在我的 activity class 中使用一个布尔值,该值在 onPrepareOptionsMenu 中检查并隐藏/显示菜单。

但是当异步任务完成时,如何从服务 class 中设置这个布尔值? 以下是 class 片段。所有建议欢迎! :)

public class LiveCardMenuActivity extends Activity {

private static final String TAG = LiveCardMenuActivity.class.getSimpleName();
// default disabled menu
private boolean menu_showForecast = false;

@Override
// in this method we hide/ show forecast menu, depending if the service has gotten the data
public boolean onPrepareOptionsMenu(Menu menu) {
    if(!menu_showForecast) {
        menu.findItem(R.id.action_show_forecast).setVisible(false);
    }
    return super.onPrepareOptionsMenu(menu);

...

并且是带有异步任务

的服务class
public class LiveCardService extends Service {

private static final String TAG = LiveCardService.class.getSimpleName();

private static final String LIVE_CARD_TAG = "LiveCardService";
private LiveCard mLiveCard;
private RemoteViews mLiveCardView;

private final Handler mHandler = new Handler();
private final UpdateLiveCardRunnable mUpdateLiveCardRunnable = new UpdateLiveCardRunnable();
private static final long DELAY_MILLIS = 1000;

// keep the weather info central, due to reuse and forecast cards
private Weather weather = new Weather();
private WeatherForecast weatherForecast = new WeatherForecast();

@Override
public IBinder onBind(Intent intent) {
    return null;
}

@Override
public void onCreate() {
    super.onCreate();

    // and get the weather data & icon, async call
    String loc = "id=2755420";
    JSONWeatherTask task = new JSONWeatherTask();
    task.execute(new String[]{loc});

    // including the weather forecast
    JSONWeatherForecastTask taskForecast = new JSONWeatherForecastTask();
    taskForecast.execute(new String[]{loc});
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    if (mLiveCard == null) {
        // Get an instance of a live card
        mLiveCard = new LiveCard(this, LIVE_CARD_TAG);

        // setup live card views
        mLiveCardView = new RemoteViews(getPackageName(), R.layout.live_card);
        mLiveCard.setViews(mLiveCardView);

        // Display the options menu when the live card is tapped.
        Intent menuIntent = new Intent(this, LiveCardMenuActivity.class);
        mLiveCard.setAction(PendingIntent.getActivity(this, 0, menuIntent, 0));
        mLiveCard.publish(PublishMode.REVEAL);

        // Queue the update text runnable
        mHandler.post(mUpdateLiveCardRunnable);
    } else {
        mLiveCard.navigate();
    }
    return START_STICKY;
}

...

private class JSONWeatherForecastTask extends AsyncTask<String, Void, WeatherForecast> {

    @Override
    protected WeatherForecast doInBackground(String... params) {
        //
        String data = ( (new WeatherHttpClient()).getWeatherForecastData(params[0]));

        try {
            weatherForecast = JSONWeatherForecastParser.getWeatherForecast(data);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return weatherForecast;
    }

    @Override
    protected void onPostExecute(WeatherForecast weatherForecast) {
        super.onPostExecute(weatherForecast);


        // there is no showing of data yet, except voor enabling the forecast menu
        Weather[] weatherForecastArray = weatherForecast.getWeatherForecastArray();
        int count = weatherForecastArray.length;
        if(count > 0){
            //mLiveCard menu update or boolean update?
        }
    }

}

Timer sample 的菜单 Activity 有一些逻辑可以根据 运行 计时器的状态动态更改其选项菜单:

  1. MenuActivityonCreate 回调中:绑定到 TimerService
  2. 绑定 Service 后,检索有关当前 Timer 的信息。
  3. 一旦满足所有状态(Activity 已附加到 Window,已检索计时器信息):打开选项菜单。

以下是来自 MenuActivity class:

的代码片段
/**
 * This activity manages the options menu that appears when the user taps on the timer's live
 * card or says "ok glass" while the live card is settled.
 */
public class MenuActivity extends Activity {

    private Timer mTimer;
    private boolean mAttachedToWindow;
    private boolean mIsMenuClosed;
    private boolean mPreparePanelCalled;
    private boolean mIsSettingTimer;

    private boolean mFromLiveCardVoice;

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            if (service instanceof TimerService.TimerBinder) {
                mTimer = ((TimerService.TimerBinder) service).getTimer();
                openMenu();
            }
            // No need to keep the service bound.
            unbindService(this);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            // Nothing to do here.
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mFromLiveCardVoice = getIntent().getBooleanExtra(LiveCard.EXTRA_FROM_LIVECARD_VOICE, false);
        if (mFromLiveCardVoice) {
            // When activated by voice from a live card, enable voice commands. The menu
            // will automatically "jump" ahead to the items (skipping the guard phrase
            // that was already said at the live card).
            getWindow().requestFeature(WindowUtils.FEATURE_VOICE_COMMANDS);
        }

        // Bind to the Timer service to retrive the current timer's data.
        Intent serviceIntent = new Intent(this, TimerService.class);
        serviceIntent.putExtra(
            TimerService.EXTRA_TIMER_HASH_CODE,
            getIntent().getIntExtra(TimerService.EXTRA_TIMER_HASH_CODE, 0));
        serviceIntent.setData(getIntent().getData());
        bindService(serviceIntent, mConnection, 0);
    }

    @Override
    public void onAttachedToWindow() {
        super.onAttachedToWindow();
        mAttachedToWindow = true;
        openMenu();
    }

    @Override
    public void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        mAttachedToWindow = false;
    }

    @Override
    public boolean onCreatePanelMenu(int featureId, Menu menu) {
        if (isMyMenu(featureId)) {
            getMenuInflater().inflate(R.menu.timer, menu);
            return true;
        }
        return super.onCreatePanelMenu(featureId, menu);
    }

    @Override
    public boolean onPreparePanel(int featureId, View view, Menu menu) {
        mPreparePanelCalled = true;
        if (isMyMenu(featureId)) {
            if (mTimer == null) {
                // Can't prepare the menu as we're not yet bound to a timer.
                return false;
            } else {
                // Disable or enable menu item depending on the Timer's state.

                // Don't reopen menu once we are finishing. This is necessary
                // since voice menus reopen themselves while in focus.
                return !mIsMenuClosed;
            }
        }
        return super.onPreparePanel(featureId, view, menu);
    }

    @Override
    public boolean onMenuItemSelected(int featureId, MenuItem item) {
        if (!isMyMenu(featureId)) {
            return super.onMenuItemSelected(featureId, item);
        }
        // Handle item selection.
    }

    @Override
    public void onPanelClosed(int featureId, Menu menu) {
        super.onPanelClosed(featureId, menu);
        if (isMyMenu(featureId)) {
            mIsMenuClosed = true;
            if (!mIsSettingTimer) {
                // Nothing else to do, closing the Activity.
                finish();
            }
        }
    }

    /**
     * Opens the touch or voice menu iff all the conditions are satifisfied.
     */
    private void openMenu() {
        if (mAttachedToWindow && mTimer != null) {
            if (mFromLiveCardVoice) {
                if (mPreparePanelCalled) {
                    // Invalidates the previously prepared voice menu now that we can properly
                    // prepare it.
                    getWindow().invalidatePanelMenu(WindowUtils.FEATURE_VOICE_COMMANDS);
                }
            } else {
                // Open the options menu for the touch flow.
                openOptionsMenu();
            }
        }
    }

    /**
     * Returns {@code true} when the {@code featureId} belongs to the options menu or voice
     * menu that are controlled by this menu activity.
     */
    private boolean isMyMenu(int featureId) {
        return featureId == Window.FEATURE_OPTIONS_PANEL ||
               featureId == WindowUtils.FEATURE_VOICE_COMMANDS;
    }
}