2

Following is my setup. I have a livecard service class which does an async task to get weather and weatherforecast data externally. It also starts a Pendingintent with two menu-items being "ShowForecast" & "Stop"

The weather data is shown on the main screen when it arrives. However the forecast data takes a bit longer. I would like to hide the ShowForecast menu until the async task completes successfully.

What is the best way to implement this? I've read something about global variables, or through intent.putextra or updating the card menu directly. What I am thinking about now is to use a boolean value in my activity class that gets checked in onPrepareOptionsMenu and hides/ shows the menu.

But how do I set this boolean from the Service class when async task completes? Below are the class snippets. All advise welcome pls! :)

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);

...

And is the service class with the async task

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?
        }
    }

}
Jinxvar
  • 871
  • 6
  • 8

1 Answers1

1

The Timer sample's menu Activity has some logic to dynamically change its options menu according to the state of the running Timer:

  1. In the MenuActivity's onCreate callback: bind to the TimerService.
  2. Once the Service is bound, retrieve information about the current Timer.
  3. Once all states are satisfied (Activity has been attached to a Window, Timer information has been retrieved): open the options menu.

Here are snippets of code from the 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;
    }
}
Alain
  • 6,044
  • 21
  • 27
  • Hi Alain, thx for the help. Does this setup with binding also work when the Service class is the main class as in my setup? This al seems to take place within one class which makes it a bit easier. Or am I missing the overall picture somewhere. :( – Jinxvar Jan 22 '15 at 19:24
  • No longer needed, solved it with your answer. I created a binder on my service class, and bound to it from the activity. From the activity I called a getter when preparing the menu. The getter checks whether the forecast has been received. Works likes a charm, thx again! – Jinxvar Jan 22 '15 at 20:56