/*
* 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.map;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.res.Resources;
import android.database.Cursor;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.support.v17.leanback.widget.VerticalGridView;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.Toast;
import com.google.android.apps.santatracker.R;
import com.google.android.apps.santatracker.data.AllDestinationCursorLoader;
import com.google.android.apps.santatracker.data.Destination;
import com.google.android.apps.santatracker.data.DestinationCursor;
import com.google.android.apps.santatracker.data.PresentCounter;
import com.google.android.apps.santatracker.data.SantaPreferences;
import com.google.android.apps.santatracker.data.StreamCursor;
import com.google.android.apps.santatracker.data.StreamCursorLoader;
import com.google.android.apps.santatracker.data.StreamEntry;
import com.google.android.apps.santatracker.map.cardstream.CardAdapter;
import com.google.android.apps.santatracker.map.cardstream.DashboardFormats;
import com.google.android.apps.santatracker.map.cardstream.DashboardViewHolder;
import com.google.android.apps.santatracker.map.cardstream.TrackerCard;
import com.google.android.apps.santatracker.service.SantaService;
import com.google.android.apps.santatracker.service.SantaServiceMessages;
import com.google.android.apps.santatracker.util.AccessibilityUtil;
import com.google.android.apps.santatracker.util.AnalyticsManager;
import com.google.android.apps.santatracker.util.Intents;
import com.google.android.apps.santatracker.util.MeasurementManager;
import com.google.android.apps.santatracker.util.SantaLog;
import com.google.android.gms.maps.model.LatLng;
import com.google.firebase.analytics.FirebaseAnalytics;
import java.lang.ref.WeakReference;
/**
* Map Activity that shows Santa's destinations and his path on and after
* Christmas.
*/
public class TvSantaMapActivity extends FragmentActivity implements
SantaMapFragment.SantaMapInterface {
private static String ARRIVING_IN, DEPARTING_IN, NO_NEXT_DESTINATION;
// countdown update frequency (in ms)
private static final int DESTINATION_COUNTDOWN_UPDATE_INTERVAL = 1000;
// Percentage of presents to hand out when travelling between destinations
// (the rest is handed out when the destination is reached)
public static final double FACTOR_PRESENTS_TRAVELLING = 0.3;
protected static final String TAG = "TvSantaMapActivity";
private static final int LOADER_DESTINATIONS = 1;
private static final int LOADER_STREAM = 2;
private CountDownTimer mTimer;
private PresentCounter mPresents = new PresentCounter();
protected DestinationCursor mDestinations;
// Fragments
protected SantaMapFragment mMapFragment;
// Activity State
private boolean mHasDataLoaded = false;
private boolean mIsLive = false;
private boolean mResumed = false;
private boolean mIgnoreNextUpdate = false;
// Resource Strings
private static String LOST_CONTACT_STRING, CURRENT_LOCATION, NEXT_LOCATION;
private static String ANNOUNCE_TRAVEL_TO;
private static String ANNOUNCE_ARRIVED_AT;
// Server controlled data
protected boolean mSwitchOff = true;
protected long mOffset = 0L;
protected long mFirstDeparture = 0L;
protected long mFinalArrival = 0L;
protected long mFinalDeparture = 0L;
// Toggle when error accessing API and need to return to Village with error message when out of
// locations
private boolean mHaveApiError = false;
// Service integration
private Messenger mService = null;
private boolean mIsBound = false;
private final Messenger mMessenger = new Messenger(new IncomingHandler(this));
// Stream
private StreamEntry mNextStreamEntry = null;
protected StreamCursor mStream;
private CardAdapter mAdapter;
private VerticalGridView mVerticalGridView;
private AccessibilityManager mAccessibilityManager;
private FirebaseAnalytics mMeasurement;
private boolean mJumpingToUserDestination = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// App Measurement
mMeasurement = FirebaseAnalytics.getInstance(this);
MeasurementManager.recordScreenView(mMeasurement, getString(R.string.analytics_screen_tracker));
// [ANALYTICS SCREEN]: Tracker
AnalyticsManager.sendScreenView(R.string.analytics_screen_tracker);
setContentView(R.layout.activity_map_tv);
mAccessibilityManager = (AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE);
Resources resources = getResources();
LOST_CONTACT_STRING = resources.getString(R.string.lost_contact_with_santa);
ANNOUNCE_ARRIVED_AT = resources.getString(R.string.santa_is_now_arriving_in_x);
ARRIVING_IN = resources.getString(R.string.arriving_in);
DEPARTING_IN = resources.getString(R.string.departing_in);
NO_NEXT_DESTINATION = resources.getString(R.string.no_next_destination);
CURRENT_LOCATION = resources.getString(R.string.current_location);
NEXT_LOCATION = resources.getString(R.string.next_destination);
// Concatenate String for 'travel to' announcement
StringBuilder sb = new StringBuilder();
sb.append(resources.getString(R.string.in_transit));
sb.append(" ");
sb.append(resources.getString(R.string.next_destination));
sb.append(" %s");
ANNOUNCE_TRAVEL_TO = sb.toString();
sb.setLength(0);
// Get Map fragments
mMapFragment = (SantaMapFragment) getSupportFragmentManager()
.findFragmentById(R.id.fragment_map);
// Set Overscan Padding
final int widthPadding
= getResources().getDimensionPixelOffset(R.dimen.overscan_padding_width);
final int heightPadding
= getResources().getDimensionPixelOffset(R.dimen.overscan_padding_height);
final int cardPadding = getResources().getDimensionPixelOffset(R.dimen.card_width);
// Right side padding is twice as much to put padding on both sides of card.
final int rightPadding = 2 * widthPadding + cardPadding;
mMapFragment.setCamPadding(widthPadding, heightPadding, rightPadding, heightPadding);
mVerticalGridView = (VerticalGridView) findViewById(R.id.stream);
mVerticalGridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
mVerticalGridView.setHasFixedSize(false);
mAdapter = new CardAdapter(
getApplicationContext(), mCardAdapterListener, mDestinationListener, true);
mAdapter.setHasStableIds(true);
mVerticalGridView.setAdapter(mAdapter);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (mResumed && hasFocus) {
mMapFragment.resumeAudio();
}
}
@Override
protected void onResume() {
super.onResume();
mResumed = true;
mVerticalGridView.requestFocus();
}
@Override
protected void onStart() {
super.onStart();
bindService(new Intent(this, SantaService.class), mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
// unregister and unbind from Services
unregisterFromService();
}
@Override
protected void onPause() {
mResumed = false;
// stop the countdown timer if running
if (mTimer != null) {
mTimer.cancel();
mTimer = null;
}
// stop santa cam
onSantacamStateChange(false);
// Reset state
mHasDataLoaded = false;
mJumpingToUserDestination = false;
mIsLive = false;
mDestinations = null;
mStream = null;
super.onPause();
}
private void unregisterFromService() {
if (mIsBound) {
if (mService != null) {
Message msg = Message
.obtain(null, SantaServiceMessages.MSG_SERVICE_UNREGISTER_CLIENT);
msg.replyTo = mMessenger;
try {
mService.send(msg);
} catch (RemoteException e) {
// ignore if service is not available
}
mService = null;
}
unbindService(mConnection);
mIsBound = false;
}
}
private LoaderManager.LoaderCallbacks<Cursor> mLoaderCallbacks
= new LoaderManager.LoaderCallbacks<Cursor>() {
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
switch (id) {
case LOADER_DESTINATIONS: {
return new AllDestinationCursorLoader(TvSantaMapActivity.this);
}
case LOADER_STREAM: {
return new StreamCursorLoader(getApplicationContext(), false);
}
}
return null;
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
final int id = loader.getId();
if (id == LOADER_DESTINATIONS) {
// loader finished loading cursor, setup the helper
mDestinations = new DestinationCursor(cursor);
start();
} else if (id == LOADER_STREAM) {
mStream = new StreamCursor(cursor);
addPastStream();
}
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
switch (loader.getId()) {
case LOADER_DESTINATIONS:
mDestinations = null;
break;
case LOADER_STREAM:
mStream = null;
break;
}
}
};
/**
* Finishes the current activity and starts the startup activity.
*/
protected void returnToStartupActivity() {
finish();
}
/**
* Call when the map or destinations are ready. Checks if both are initialised and calls
* startTracking if ready.
*/
private void start() {
// check that the cursor and map have been initialised
if (mDestinations == null || !mMapFragment.isInitialised()) {
return;
}
if (!mIsLive) {
startTracking();
}
}
/**
* Moves the destination cursor from to the current destination and adds all visited locations
* to the map.
*/
protected void addVisitedLocations() {
// add all visited destinations from the cursors current position to the map
while (mDestinations.hasNext()
&& mDestinations.isInPast(SantaPreferences.getCurrentTime())) {
Destination destination = mDestinations.getCurrent();
mMapFragment.addLocation(destination);
mAdapter.addDestination(false, destination, false);
mDestinations.moveToNext();
}
mAdapter.notifyDataSetChanged();
}
/**
* Displays a friendly toast and returns to the startup activity with the given message.
*/
private void handleErrorFinish() {
Log.d(TAG, "Lost contact, returning to village.");
Toast.makeText(getApplicationContext(), LOST_CONTACT_STRING,
Toast.LENGTH_LONG).show();
returnToStartupActivity();
}
/**
* Called when the map has been initialised and is ready to be used.
*/
public void onMapInitialised() {
// map initialised, start tracking
start();
}
/**
* Start tracking Santa. If Santa is already finished, return to the main launcher. All
* destinations from the cursor's current position to the current time are added to the map and
* the map is restored to its
*/
protected void startTracking() {
mIsLive = true;
final long time = SantaPreferences.getCurrentTime();
// Return to launch activity if Santa hasn't left yet or has already left for the next year
if (time >= mFirstDeparture && time < mFinalArrival) {
// It's Christmas and Santa is travelling
startOnChristmas();
} else {
// Any other state, return back to Village
returnToStartupActivity();
}
}
private void startOnChristmas() {
SantaLog.d(TAG, "start on christmas");
addVisitedLocations();
// Load the stream data once all past locations have been added, based on the last visited
// location
getSupportLoaderManager()
.restartLoader(LOADER_STREAM, null, mLoaderCallbacks);
// determine santa's status - visiting or travelling?
if (!mDestinations.hasNext()) {
// sanity check - already finished, no destinations left
returnToStartupActivity();
} else if (mDestinations.isVisiting(SantaPreferences.getCurrentTime())) {
// currently visiting a location
Destination d = mDestinations.getCurrent();
// move santa marker
visitDestination(d, false);
setNextDestination(d);
// enable santa cam and center on santa
mMapFragment.enableSantaCam(true);
} else {
// not currently visiting a location, en route to next destination
// enable santacam, but do not move camera - this is done
// through a callback once the santa animation has started
mMapFragment.enableSantaCam(true);
// get the destination and animate santa
Destination d = mDestinations.getCurrent();
// animate to next destination
// marker at origin has already been set above, does not need to be
// added again.
travelToDestination(null, d);
}
}
/**
* Call when Santa is en route to the given destination.
*/
private void travelToDestination(final Destination origin,
final Destination nextDestination) {
if (origin != null) {
// add marker at origin position to map.
mMapFragment.addLocation(origin);
}
// check if finished
if (mDestinations.isFinished() || nextDestination == null) {
// App Measurement
MeasurementManager.recordCustomEvent(mMeasurement,
getString(R.string.analytics_event_category_tracker),
getString(R.string.analytics_tracker_action_finished),
getString(R.string.analytics_tracker_error_nodata));
// [ANALYTICS EVENT]: Error NoData after API error
AnalyticsManager.sendEvent(R.string.analytics_event_category_tracker,
R.string.analytics_tracker_action_finished,
R.string.analytics_tracker_error_nodata);
// No more destinations left, return to village
returnToStartupActivity();
return;
}
if (mHaveApiError) {
// App Measurement
MeasurementManager.recordCustomEvent(mMeasurement,
getString(R.string.analytics_event_category_tracker),
getString(R.string.analytics_tracker_action_error),
getString(R.string.analytics_tracker_error_nodata));
// [ANALYTICS EVENT]: Error NoData after API error
AnalyticsManager.sendEvent(R.string.analytics_event_category_tracker,
R.string.analytics_tracker_action_error,
R.string.analytics_tracker_error_nodata);
handleErrorFinish();
return;
}
final String nextString = DashboardFormats.formatDestination(nextDestination);
setNextLocation(nextString);
setNextDestination(nextDestination);
setCurrentLocation(null);
// get the previous position
Destination previous = mDestinations.getPrevious();
SantaLog.d(TAG, "Travel: " + (origin != null ? origin.identifier : "null") + " -> "
+ nextDestination.identifier +
" prev=" + (previous != null ? previous.identifier : "null"));
// if this is the very first location, move santa directly
if (previous == null) {
mMapFragment.setSantaVisiting(nextDestination, false);
mPresents.init(0,
nextDestination.presentsDelivered, nextDestination.arrival,
nextDestination.departure);
} else {
mMapFragment.setSantaTravelling(previous, nextDestination, !mJumpingToUserDestination);
// only hand out X% of presents during travel
long presentsEnd = previous.presentsDelivered + Math
.round((nextDestination.presentsDeliveredAtDestination)
* FACTOR_PRESENTS_TRAVELLING);
mPresents.init(previous.presentsDelivered,
presentsEnd, previous.departure,
nextDestination.arrival);
}
// Notify dashboard to send accessibility event
AccessibilityUtil.announceText(String.format(ANNOUNCE_TRAVEL_TO, nextString),
mVerticalGridView, mAccessibilityManager);
// cancel the countdown if it is already running
if (mTimer != null) {
mTimer.cancel();
}
mTimer = new CountDownTimer(nextDestination.arrival - SantaPreferences.getCurrentTime(),
DESTINATION_COUNTDOWN_UPDATE_INTERVAL) {
@Override
public void onTick(long millisUntilFinished) {
countdownTick();
}
@Override
public void onFinish() {
// reached destination - visit destination
visitDestination(nextDestination, true);
}
};
if (mResumed) {
mTimer.start();
}
}
private DashboardViewHolder getDashboardViewHolder() {
return (DashboardViewHolder) mVerticalGridView.findViewHolderForItemId(mAdapter.getDashboardId());
}
private void setCurrentLocation(String location) {
final DashboardViewHolder holder = getDashboardViewHolder();
if (holder == null) {
return;
}
if (TextUtils.isEmpty(location)) {
holder.countdownLabel.setText(ARRIVING_IN);
} else {
holder.countdownLabel.setText(DEPARTING_IN);
holder.locationLabel.setText(CURRENT_LOCATION);
holder.location.setText(location);
}
}
private void setNextLocation(final String s) {
final String nextLocation = s == null ? NO_NEXT_DESTINATION : s;
mAdapter.setNextLocation(nextLocation);
final DashboardViewHolder holder = getDashboardViewHolder();
if (null == holder) {
return;
}
holder.location.post(new Runnable() {
@Override
public void run() {
holder.locationLabel.setText(NEXT_LOCATION);
holder.location.setText(nextLocation);
}
});
}
private void setNextDestination(Destination next) {
mAdapter.addDestination(false, next, false);
}
/**
* Call when Santa is to visit a location.
*/
private void visitDestination(final Destination destination, boolean playSound) {
// Only visit this location if there is a following destination
// Otherwise out of data or at North Pole
if (mDestinations.isLast()) {
// App Measurement
MeasurementManager.recordCustomEvent(mMeasurement,
getString(R.string.analytics_event_category_tracker),
getString(R.string.analytics_tracker_action_error),
getString(R.string.analytics_tracker_error_nodata));
// [ANALYTICS EVENT]: Error NoData
AnalyticsManager.sendEvent(R.string.analytics_event_category_tracker,
R.string.analytics_tracker_action_error,
R.string.analytics_tracker_error_nodata);
Toast.makeText(this, R.string.lost_contact_with_santa, Toast.LENGTH_LONG).show();
returnToStartupActivity();
return;
}
Destination nextDestination = mDestinations.getPeekNext();
SantaLog.d(TAG, "Arrived: " + destination.identifier + " current=" + mDestinations
.getCurrent().identifier + " next = " + nextDestination + " next id="
+ nextDestination);
// hand out the remaining presents for this location, explicit to ensure counter is always
// in correct state and does not depend on anything else at runtime.
final long presentsStart = destination.presentsDelivered -
destination.presentsDeliveredAtDestination +
Math.round(
(destination.presentsDeliveredAtDestination)
* (1.0f - FACTOR_PRESENTS_TRAVELLING)
);
mPresents.init(presentsStart, destination.presentsDelivered,
destination.arrival, destination.departure);
final String destinationString = DashboardFormats.formatDestination(destination);
setCurrentLocation(destinationString);
mMapFragment.setSantaVisiting(destination, playSound);
// Notify dashboard to send accessibility event
AccessibilityUtil
.announceText(String.format(ANNOUNCE_ARRIVED_AT, destination.getPrintName()),
mVerticalGridView, mAccessibilityManager);
// cancel the countdown if it is already running
if (mTimer != null) {
mTimer.cancel();
}
// Count down until departure
mTimer = new CountDownTimer(destination.departure
- SantaPreferences.getCurrentTime(),
DESTINATION_COUNTDOWN_UPDATE_INTERVAL) {
@Override
public void onTick(long millisUntilFinished) {
countdownTick();
}
@Override
public void onFinish() {
// finished at this destination, move to the next one
travelToDestination(mDestinations.getCurrent(),
mDestinations.getNext());
}
};
if (mResumed) {
mTimer.start();
}
}
private void setDestinationPhotoDisabled(boolean disablePhoto) {
mAdapter.setDestinationPhotoDisabled(disablePhoto);
}
private void setPresentsDelivered(final String presentsDelivered) {
DashboardViewHolder holder = getDashboardViewHolder();
if (holder == null) {
return;
}
holder.presents.setText(presentsDelivered);
}
private void countdownTick() {
final long presents = mPresents
.getPresents(SantaPreferences.getCurrentTime());
final String presentsString = DashboardFormats.formatPresents(presents);
setPresentsDelivered(presentsString);
// Check if next stream card should be displayed
if (mNextStreamEntry != null && mStream != null &&
SantaPreferences.getCurrentTime() >= mNextStreamEntry.timestamp) {
addStreamEntry(mNextStreamEntry);
mNextStreamEntry = mStream.getNext();
}
}
private void addPastStream() {
// add all visited destinations from the cursors current position to the map
StreamEntry next = mStream.getCurrent();
while (next != null && next.timestamp < SantaPreferences.getCurrentTime()) {
addStreamEntry(mStream.getCurrent());
next = mStream.getNext();
}
mNextStreamEntry = next;
}
private void addStreamEntry(StreamEntry entry) {
SantaLog.d(TAG, "Add Stream entry: " + entry.timestamp);
TrackerCard card = mAdapter.addStreamEntry(entry);
announceNewCard(card);
}
private void announceNewCard(TrackerCard card) {
if (mAccessibilityManager == null) {
return;
}
String text = null;
if (card instanceof TrackerCard.FactoidCard) {
text = getString(R.string.new_trivia_from_santa);
} else if (card instanceof TrackerCard.MovieCard) {
text = getString(R.string.new_video_from_santa);
} else if (card instanceof TrackerCard.PhotoCard) {
text = getString(R.string.new_photo_from_santa);
} else if (card instanceof TrackerCard.StatusCard) {
text = getString(R.string.new_update_from_santa);
}
if (text != null) {
// Announce the new card
AccessibilityUtil.announceText(text, mVerticalGridView, mAccessibilityManager);
}
}
@Override
public void onShowDestination(Destination destination) {
// Nothing to do on TV
}
@Override
public void onClearDestination() {
// Nothing to do on TV
}
/**
* Called when the map is clicked
*/
@Override
public void mapClickAction() {
// Nothing to do
}
@Override
public void onSantacamStateChange(boolean santacamEnabled) {
// Noting to do
}
private CardAdapter.DestinationCardKeyListener mDestinationListener
= new CardAdapter.DestinationCardKeyListener() {
@Override
public void onJumpToDestination(LatLng destination) {
if (mMapFragment == null || !mMapFragment.isInitialised()) {
return;
}
if (!mJumpingToUserDestination) {
Log.d(TAG, "onJumpToDestination:" + destination.toString());
mJumpingToUserDestination = true;
mMapFragment.jumpToDestination(destination);
}
}
@Override
public void onFinish() {
if (mMapFragment == null || !mMapFragment.isInitialised()) {
return;
}
mJumpingToUserDestination = false;
Log.d(TAG, "onJumpToDestinationFinish.");
mMapFragment.enableSantaCam(true);
}
@Override
public boolean onMoveBy(KeyEvent event) {
// TODO (chansuk) Allow a user to move around the map by using D-Pad
return false;
}
};
private CardAdapter.CardAdapterListener mCardAdapterListener
= new CardAdapter.CardAdapterListener() {
@Override
public void onOpenStreetView(Destination.StreetView streetView) {
// App Measurement
MeasurementManager.recordCustomEvent(mMeasurement,
getString(R.string.analytics_event_category_tracker),
getString(R.string.analytics_tracker_action_streetview),
streetView.id);
// [ANALYTICS EVENT]: StreetView
AnalyticsManager.sendEvent(R.string.analytics_event_category_tracker,
R.string.analytics_tracker_action_streetview,
streetView.id);
Intent intent = Intents.getStreetViewIntent(getString(R.string.streetview_uri), streetView);
startActivity(intent);
}
@Override
public void onPlayVideo(String youtubeId) {
// App Measurement
MeasurementManager.recordCustomEvent(mMeasurement,
getString(R.string.analytics_event_category_tracker),
getString(R.string.analytics_tracker_action_video),
youtubeId);
// [ANALYTICS EVENT]: Video
AnalyticsManager.sendEvent(R.string.analytics_event_category_tracker,
R.string.analytics_tracker_action_video,
youtubeId);
Intent intent = Intents.getYoutubeIntent(mVerticalGridView.getContext(), youtubeId);
startActivity(intent);
}
};
private static class IncomingHandler extends Handler {
private final WeakReference<TvSantaMapActivity> mActivityRef;
public IncomingHandler(TvSantaMapActivity activity) {
mActivityRef = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
SantaLog.d(TAG, "message=" + msg.what);
final TvSantaMapActivity activity = mActivityRef.get();
if (activity == null) {
return;
}
if (!activity.mIgnoreNextUpdate ||
msg.what == SantaServiceMessages.MSG_SERVICE_STATUS) {
// ignore all updates while flag is toggled until status update is received
switch (msg.what) {
case SantaServiceMessages.MSG_SERVICE_STATE_BEGIN:
// beginning full state update, ignore if already live
if (activity.mIsLive) {
activity.mIgnoreNextUpdate = true;
}
break;
case SantaServiceMessages.MSG_SERVICE_STATUS:
// Current state of service, received once when connecting, reset ignore
activity.mIgnoreNextUpdate = false;
switch (msg.arg1) {
case SantaServiceMessages.STATUS_IDLE:
activity.mHaveApiError = false;
if (!activity.mHasDataLoaded) {
activity.mHasDataLoaded = true;
activity.getSupportLoaderManager()
.restartLoader(LOADER_DESTINATIONS, null,
activity.mLoaderCallbacks);
}
break;
case SantaServiceMessages.STATUS_ERROR_NODATA:
case SantaServiceMessages.STATUS_ERROR:
Log.d(TAG, "Santa tracking error 3, continue for now");
activity.mHaveApiError = true;
break;
case SantaServiceMessages.STATUS_PROCESSING:
// wait for success, but tell user we are waiting
Toast.makeText(activity, R.string.contacting_santa,
Toast.LENGTH_LONG).show();
activity.mHaveApiError = false;
break;
}
break;
case SantaServiceMessages.MSG_INPROGRESS_UPDATE_ROUTE:
Log.d(TAG, "Santa tracking update 0 - returning.");
// route is about to be updated, return to StartupActivity
activity.handleErrorFinish();
break;
case SantaServiceMessages.MSG_UPDATED_STREAM:
// stream data has been updated - requery data
if (activity.mHasDataLoaded && activity.mStream != null) {
Log.d(TAG, "Santa stream update received.");
activity.getSupportLoaderManager().restartLoader(LOADER_STREAM, null,
activity.mLoaderCallbacks);
}
break;
case SantaServiceMessages.MSG_UPDATED_ROUTE:
// route data has been updated - requery data
if (activity.mHasDataLoaded && activity.mDestinations != null) {
Log.d(TAG, "Santa tracking update 1 received.");
activity.getSupportLoaderManager().restartLoader(LOADER_DESTINATIONS,
null, activity.mLoaderCallbacks);
}
break;
case SantaServiceMessages.MSG_UPDATED_ONOFF:
// exit if flag has been set
activity.mSwitchOff = (msg.arg1 == SantaServiceMessages.SWITCH_OFF);
if (activity.mSwitchOff) {
Log.d(TAG, "Lost Santa.");
if (mActivityRef.get() != null) {
// App Measurement
Context context = mActivityRef.get();
FirebaseAnalytics measurement = FirebaseAnalytics.getInstance(context);
MeasurementManager.recordCustomEvent(measurement,
context.getString(R.string.analytics_event_category_tracker),
context.getString(R.string.analytics_tracker_action_error),
context.getString(R.string.analytics_tracker_error_switchoff));
}
// [ANALYTICS EVENT]: Error SwitchOff
AnalyticsManager.sendEvent(R.string.analytics_event_category_tracker,
R.string.analytics_tracker_action_error,
R.string.analytics_tracker_error_switchoff);
activity.handleErrorFinish();
}
break;
case SantaServiceMessages.MSG_UPDATED_TIMES:
onMessageUpdatedTimes(activity, msg);
break;
case SantaServiceMessages.MSG_UPDATED_DESTINATIONPHOTO:
final boolean disablePhoto = msg.arg1 == SantaServiceMessages.DISABLED;
activity.setDestinationPhotoDisabled(disablePhoto);
break;
case SantaServiceMessages.MSG_ERROR_NODATA:
//for no data: wait to run out of locations, proceed with normal error handling
case SantaServiceMessages.MSG_ERROR:
// Error accessing the API - ignore and run until out of locations
Log.d(TAG, "Couldn't track Santa, continue for now.");
activity.mHaveApiError = true;
break;
case SantaServiceMessages.MSG_SUCCESS:
activity.mHaveApiError = false;
// If data has been received for first time, start tracking
// Otherwise ignore all other updates
if (!activity.mHasDataLoaded) {
activity.mHasDataLoaded = true;
activity.getSupportLoaderManager().restartLoader(LOADER_DESTINATIONS,
null, activity.mLoaderCallbacks);
}
break;
default:
super.handleMessage(msg);
break;
}
}
}
private static boolean hasSignificantChange(long newOffset, TvSantaMapActivity activity) {
return newOffset >
activity.mOffset + SantaPreferences.OFFSET_ACCEPTABLE_RANGE_DIFFERENCE ||
newOffset <
activity.mOffset - SantaPreferences.OFFSET_ACCEPTABLE_RANGE_DIFFERENCE;
}
private void onMessageUpdatedTimes(TvSantaMapActivity activity, Message msg) {
Bundle b = (Bundle) msg.obj;
long newOffset = b.getLong(SantaServiceMessages.BUNDLE_OFFSET);
// If offset has changed significantly, return to village
if (activity.mHasDataLoaded && hasSignificantChange(newOffset, activity)) {
Log.d(TAG, "Santa tracking update 2 - returning.");
if (mActivityRef.get() != null) {
// App Measurement
Context context = mActivityRef.get();
FirebaseAnalytics measurement = FirebaseAnalytics.getInstance(context);
MeasurementManager.recordCustomEvent(measurement,
context.getString(R.string.analytics_event_category_tracker),
context.getString(R.string.analytics_tracker_action_error),
context.getString(R.string.analytics_tracker_error_timeupdate));
}
// [ANALYTICS EVENT]: Error TimeUpdate
AnalyticsManager.sendEvent(R.string.analytics_event_category_tracker,
R.string.analytics_tracker_action_error,
R.string.analytics_tracker_error_timeupdate);
activity.handleErrorFinish();
} else if (!activity.mHasDataLoaded && newOffset != activity.mOffset) {
// New offset but data has not been loaded yet, cache new offset
activity.mOffset = newOffset;
SantaPreferences.cacheOffset(activity.mOffset);
}
activity.mFinalArrival = b.getLong(SantaServiceMessages.BUNDLE_FINAL_ARRIVAL);
activity.mFinalDeparture = b.getLong(SantaServiceMessages.BUNDLE_FINAL_DEPARTURE);
activity.mFirstDeparture = b.getLong(SantaServiceMessages.BUNDLE_FIRST_DEPARTURE);
}
}
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = new Messenger(service);
mIsBound = true;
//reply with local Messenger to establish bi-directional communication
Message msg = Message.obtain(null, SantaServiceMessages.MSG_SERVICE_REGISTER_CLIENT);
msg.replyTo = mMessenger;
try {
mService.send(msg);
} catch (RemoteException e) {
// Could not connect to Service, connection will be terminated soon.
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
mService = null;
mIsBound = false;
}
};
}