/*
* Copyright (C) 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.apps.santatracker.launch;
import android.app.SearchManager;
import android.content.Intent;
import android.content.res.Resources;
import android.util.Log;
import android.view.View;
import com.google.android.apps.santatracker.R;
import com.google.android.apps.santatracker.map.SantaMapActivity;
import com.google.android.apps.santatracker.map.TvSantaMapActivity;
import com.google.android.apps.santatracker.util.SantaLog;
import com.google.android.gms.actions.SearchIntents;
import java.util.Arrays;
import java.util.concurrent.Executor;
/**
* Launch the Santa Tracker screen.
* This is only a place holder implementation that only displays a message when the tracker
* should be launched.
*/
public class LaunchSanta extends AbstractLaunch {
protected static final String TAG = "LaunchSanta";
private SimpleCommandExecutor mExecutor = new SimpleCommandExecutor();
private final Class mLaunchActivityClass;
public LaunchSanta(SantaContext context, LauncherDataChangedCallback adapter) {
super(context, adapter, R.string.track_santa, R.drawable.android_game_cards_track_santa);
if (isTV()) {
mLaunchActivityClass = TvSantaMapActivity.class;
} else {
mLaunchActivityClass = SantaMapActivity.class;
}
}
static public int getId() {
return R.string.track_santa;
}
@Override
public String getVerb() {
return mContentDescription;
}
@Override
public String getContentDescription() {
return null;
}
@Override
public void onClick(View v) {
mExecutor.cancelAll(); // touchscreen action cancels all pending voice commands
switch (mState) {
case STATE_READY:
mContext.launchActivityDelayed(
new Intent(mContext.getActivityContext(), mLaunchActivityClass), v);
break;
case STATE_LOCKED:
notify(mContext.getApplicationContext(), R.string.santa_locked);
break;
case STATE_FINISHED:
notify(mContext.getApplicationContext(), R.string.santa_is_busy_preparing_for_next_year);
break;
case STATE_DISABLED:
default:
notify(mContext.getApplicationContext(), R.string.still_trying_to_reach_santa);
break;
}
}
@Override
public boolean onLongClick(View v) {
switch (mState) {
case STATE_READY:
notify(mContext.getApplicationContext(), R.string.track_santa);
break;
case STATE_LOCKED:
notify(mContext.getApplicationContext(), R.string.santa_locked);
break;
case STATE_FINISHED:
notify(mContext.getApplicationContext(), R.string.santa_is_busy_preparing_for_next_year);
break;
case STATE_DISABLED:
default:
notify(mContext.getApplicationContext(), R.string.still_trying_to_reach_santa);
break;
}
return true;
}
@Override
public boolean handleVoiceAction(Intent intent) {
String action = intent.getAction();
if (SearchIntents.ACTION_SEARCH.equals(action)) {
String query = intent.getStringExtra(SearchManager.QUERY);
Log.d(TAG, String.format("Voice command: search for [%s] on Santa Tracker", query));
if (isSupportedQuery(query)) {
handleVoiceSearchForSanta();
// if we got here, the voice command syntax was OK
// so even though we may not actually pop the Santa map view, we'll
// report back that we handled (or at least tried to) the voice command
return true;
}
} else if (VoiceAction.ACTION_SHOW_SANTA.equals(action)) {
String name = intent.getStringExtra(VoiceAction.ACTION_SHOW_SANTA_EXTRA);
Log.d(TAG, String.format("Voice command: show [%s]", name));
handleVoiceSearchForSanta();
return true;
}
return false;
}
private void handleVoiceSearchForSanta() {
switch (mState) {
case STATE_READY: // highly unlikely to be ready upon the first invocation
SantaLog.d(TAG, "Got lucky. Launching SantaMapActivity.");
mContext.launchActivity(
new Intent(mContext.getActivityContext(), mLaunchActivityClass));
break;
case STATE_FINISHED: // FINISHED -> READY transition does happen with local API
case STATE_DISABLED:
case STATE_LOCKED:
default:
notify(mContext.getApplicationContext(), R.string.contacting_santa);
scheduleVoiceCommand();
break;
}
}
private void scheduleVoiceCommand() {
Log.d(TAG, "Not ready. Scheduling for later.");
Runnable launchSantaMap = new Runnable() {
@Override
public void run() {
Log.d(TAG, "Launching SantaMapActivity.");
mContext.launchActivity(
new Intent(mContext.getActivityContext(), mLaunchActivityClass));
}
};
mExecutor.execute(launchSantaMap);
}
private boolean isSupportedQuery(String query) {
Resources res = mContext.getResources();
String[] supportedQueries = res.getStringArray(R.array.voice_command_search_for);
return Arrays.asList(supportedQueries).contains(query.toLowerCase());
}
@Override
public void setState(int state) {
super.setState(state);
SantaLog.v(TAG, String.format("setState to [%d]", state));
// if the transition was into READY state, execute all pending commands
if (mState == STATE_READY) {
mExecutor.executeAll();
}
}
/**
* Queues up commands until explicit executeAll invocation.
* Currently max queue length is 1.
*/
static class SimpleCommandExecutor implements Executor {
public static final int TIMEOUT_MS = 30 * 1000; // give up after 30 seconds
private Runnable mPendingCommand;
private long mTimeStamp;
@Override
public void execute(Runnable command) {
synchronized (this) {
cancelAll();
mPendingCommand = command;
mTimeStamp = System.currentTimeMillis();
}
}
public synchronized void cancelAll() {
mPendingCommand = null;
mTimeStamp = 0L;
}
public synchronized void executeAll() {
if (mPendingCommand != null) {
if (System.currentTimeMillis() - mTimeStamp <= TIMEOUT_MS) {
mPendingCommand.run();
} else {
Log.d(TAG, "Pending command timed out, ignoring.");
}
mPendingCommand = null;
mTimeStamp = 0L;
}
}
}
@Override
public boolean isGame() {
return false;
}
}